티스토리 뷰

Flutter

ChangeNotifierProvider 간단 사용기

순진이 2024. 6. 21. 19:56

Udemy에서 Provider 강의를 보며 정리한 내용!

https://www.udemy.com/course/flutter-provider-essential-korean/?couponCode=24T6MT62024

 

 


Provider는 플러터에서 가장 기초되는 상태 관리 패키지임.

그 중에 ChangeNotifierProvider에 대해서 알아보겠음

 

이를 위해서는 ChangeNotifier에 대해 알아야 하는데, 

ChangeNotifier는 상태 관리 객체이며, 상태 변경 시 notifyListners()를 호출하여 모든 리스너들에게 알림을 보냄 

"야~ 내 상태 변해썽~~" -> 이를 반영할 필요가 있는 위젯들은 UI를 다시 그림

 

ChangeNotifierProvider는 ChangeNotifier 객체를 생성하고 관리하며, 이를 위젯 트리에 제공하고 하위 위젯들이 상태를 구독하고 사용할 수 있게 함. 

"ㅇㅇ. ㅇㅋ~"

 


간단한 실습을 통해 이해해보겠음

 

0. ChangeNotifier를 상속받는 class 만들기

우리집 뭉치(파피용 강아지) 클래스를 만들고, 이름, 견종, 나이 속성을 갖게 했음 

 

★ 제일 중요한 ChangeNotifier 상속하기  잊지말 것

 

그리고 age를 조작하는 함수 2개 만듦

-> upAge() 함수에는 실행될 때마다 age + 1을 해주고,

-> resetAge()에서는 다시 age를 1로 리셋하는 함수를 만들었음

-> upAge(), resetAge()에서 변경한 age 값을 notifyListner()하여 모든 리스너들에게 알림!

import 'package:flutter/material.dart';

class Moongchi extends ChangeNotifier {
  final String name = 'Moongchi';
  final String breed = 'Papillon';
  int age = 1;

  upAge() {
    age++;
    notifyListeners();
  }

  resetAge() {
    age = 1;
    notifyListeners();
  }
}

 

 

1. 위젯트리에 ChangeNotifierProvier를 선언해주기

ChangeNotifierProvier뿐만 아니라 Provider는 결국 위젯임. 그렇기 때문에 위젯 트리에 위치해야 하며, 위젯 트리의 Provider 아래에 있는 위젯들은 트리를 통해 Provider에 접근할 수 있게 되는 것

 

여기서는 ChangeNotifierProvier로 MaterialApp을 감싸줄 예정인데, 

그 전에 ChangeNotifierProvier의 정의를 보면, 아래와 같음

 

ChangeNotifierProvier는 create라는 required 파라미터를 갖는데, 이 부분이 중요

create는 ChangeNotifierProvier가 관리하는 ChangeNotifier의 객체를 생성하는 함수임!

BuildContext를 매개변수로 받아 ChangeNotifier의 객체를 반환함.

 

여기서는 ChnageNotifier를 상속받은 Moonchi 객체가 되겠음 <- Moongchi()

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Moongchi>(
      create: (context) => Moongchi(),
      child: MaterialApp(
        title: 'Provider',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

 

위젯 트리는 아래와 같고, 이제부터 ChangeNotifierProvier 하위 위젯에서는 Moongchi 타입의 객체에 모두 접근이 가능!

 

 


2. 값 가져다 쓰기

우리의 목적은 해당 값을 가져다 쓰는 거 아니겠음?!

Provider.of<T>로도 접근이 가능하지만 여기서는 extension methods read/watch/select를 사용하겠음 (Provider 4.1 버전부터 가능)

 

extension method 들은 아래와 같음.

 

context.read<T> -> T

- 값 읽어오기 (이후 값이 변경되더라도 위젯은 다시 빌드되지 않음)

 

context.watch<T> -> T

- 값 읽어오기 + 값 변경까지 Listen

- 값이 변경되는 위젯이 다시 빌드 됨

 

context.select<T, R>(R selector(T value)) -> R

- 특정 타입(R)의 값/속성만 읽어오기 -> 해당 값/속성만을 선택해서 구독함

- R에 가져올 값의 타입(String, int)을 명시해주고, T의 객체를 받아 R 타입의 속성을 리턴해줌

 

이 앱에서는 name과 breed는 값이 변경될 일이 없으니 context.read를,

그리고 age는 값이 변경될 경우 다시 UI에 반영해야 하므로 context.watch를 써주면 되겠음

 

또한 upAge(), resetAge()는 context.read()가 맞음 (초반에 이게 헷갈렸음..,,,,함수 내부에서 값을 변경시키는 건 맞지만, 구독이 필요한 건 age 속성이지 upAge(), resetAge() 함수가 아님. ) 

class MyHomePage extends StatelessWidget {
  MyHomePage({super.key});

  TextStyle labelStyle = const TextStyle(fontSize: 30, color: Colors.black);
  TextStyle buttonStyle = const TextStyle(fontSize: 20, color: Colors.purple);

  @override
  Widget build(BuildContext context) {
    debugPrint('MyHomePage build');
    return Scaffold(
      backgroundColor: Colors.purple[100],
      appBar: AppBar(
        title: const Text('Provider'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              // ⭐️⭐️⭐️
              'name: ${context.read<Moongchi>().name}',
              style: labelStyle,
            ),
            const SizedBox(height: 10.0),
            Text(
              // ⭐️⭐️⭐️
              'breed: ${context.read<Moongchi>().breed}',
              style: labelStyle,
            ),
            const SizedBox(height: 10.0),
            Divider(
              color: Colors.purple[400],
              thickness: 2,
            ),
            Text(
              // ⭐️⭐️⭐️
              'age: ${context.watch<Moongchi>().age}',
              style: labelStyle,
            ),
            const SizedBox(height: 20.0),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                OutlinedButton(
                  onPressed: () {
                    // ⭐️⭐️⭐️
                    context.read<Moongchi>().resetAge();
                  },
                  child: Text(
                    '나이 리셋',
                    style: buttonStyle,
                  ),
                ),
                const SizedBox(width: 20),
                OutlinedButton(
                  onPressed: () {
                    // ⭐️⭐️⭐️
                    context.read<Moongchi>().upAge();
                  },
                  child: Text(
                    '나이 추가',
                    style: buttonStyle,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

 

 

이렇게 하고 실행을 하면 정상적으로 실행이 되는 걸 알 수 있음

 



그런데 위에 context.select()에 대해서도 써놨지 않았겠음?

context.select<t, R>(R selector(T value)) -> R

그걸 바탕으로 age를 바꿔보면 아래와 같음 -> 앱도 동일하게 작동

가져올 타입 int를 명시해주고, Moongchi의 객체(mc)를 받아 int 타입의 속성(age)을 리턴해줌

 

※ 그러나 context.select()로는 upAge(), resetAge() 같은 객체 내 함수는 실행이 불가능하므로 주의

Text(
  'age: ${context.select<Moongchi, int>((Moongchi mc) => mc.age)}',
  //'age: ${context.read<Moongchi>().age}',
  ...
)

 

 

name과 breed도 써보면 아래와 같음

context.read<Moongchi>().name
context.select<Moongchi, String>((Moongchi mc) => mc.name
//context.read<Moongchi>().breed
context.select<Moongchi, String>((Moongchi mc) => mc.breed

끝!

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크