이전 시간에는 Provider를 이루고 있는 Inherited Widget의 구성과 해당 위젯으로 상태관리를 하는 방법에 대해 알아보았습니다.
이번 시간에는 Provider가 Inherited Widget에 추가한 기능, 자동 화면 리빌드를 도와주는 notifyListeners() 메서드에 대해서 알아보려고 합니다.
그러기 위해서는 ChangeNotifierProvider와 ChangeNotifier라는 두 가지 클래스를 살펴보려고 합니다.
class ChangeNotifierProvider<T extends ChangeNotifier?>
extends ListenableProvider<T> {
ChangeNotifierProvider({
Key? key,
required Create<T> create,
bool? lazy,
TransitionBuilder? builder,
Widget? child,
}) : super(
key: key,
create: create,
dispose: _dispose,
lazy: lazy,
builder: builder,
child: child,
);
// ...생략
}
ChangeNotifierProvider
는 가장 흔하게 쓰는 Provider의 위젯입니다.
아마 대부분 사용해 보셨을거에요. Provider의 상태 관리 기능과 ChangeNotifier의 자동 화면 리빌드가 함께 붙어있죠.
이는 아래 구조로 상속되어 있습니다.
InheritedProvider
-> ListenableProvider
---> ChangeNotifierProvider
그리고 ChangeNotifierProvider
클래스는 ChangeNotifier
의 하위 타입인<T extends ChangeNotifier?>
를 반환합니다.
InheritedProvider
기반으로 상태 관리를 해주며, ChangeNotifier.notifyListeners
가 호출 될 때 마다 의존성이 있는 위젯들을 모두 다시 빌드합니다.
즉, 값을 감시하다가 값의 변동이 감지되면 build()함수를 호출합니다.
Provider라이브러리의 notifyListeners()는 ChangeNotifier클래스를 기반으로 하는 상태 관리에서 핵심적인 역할을 합니다.
ChangeNotifier는 Flutter의 Foundation 패키지에 포함된 클래스이며, 상태가 변경되었음을 구독자(Listeners)에게 알리는 역할을 수행합니다.
그럼 우선 Flutter내부의 ChangeNotifier의 내부 구현 코드를 보겠습니다.
(주석은 제외함)
// flutter/lib/src/foundation/change_notifier.dart
class ChangeNotifier {
ObserverList<VoidCallback>? _listeners = ObserverList<VoidCallback>();
void addListener(VoidCallback listener) {
_listeners?.add(listener);
}
void removeListener(VoidCallback listener) {
_listeners?.remove(listener);
}
void notifyListeners() {
if (_listeners != null) {
for (final VoidCallback listener in List<VoidCallback>.from(_listeners!)) {
listener();
}
}
}
}
위의 코드에서 보면 _listeners 리스트에 리빌드가 필요한 위젯의 콜백 함수(listener)를 저장합니다.
notifyListeners()
가 호출되면, _listeners에 저장된 모든 리스너가 실행되면서 여기에 등록된 build()함수가 호출되며,
addListener()를 호출한 순서대로(FIFO, First-In-First-Out) 실행됩니다. 이런 식으로
notifyListeners()는 Flutter안에서 우리의 친구인
setState()`처럼 동작하여 화면을 다시 그리도록 하는 핵심 메커니즘 입니다.
(이해하셨다면 여기는 예시 부분이니 안 보셔도 괜찮습니다)
notifyListeners()
를 호출하여 UI가 자동으로 갱신되도록 할 수 있습니다.1. ChangeNotifier를 상속한 모델 클래스 생성
이전 시간에 만든 counter모델 클래스와 동일합니다.
import 'package:flutter/material.dart';
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 값 변경 알림~~
}
}
increment()
메서드를 호출하면 _count 값을 증가시키고, notifyListeners()
를 호출하여 UI가 변경되었음을 알립니다.2. ChangeNotifierProvider로 모델을 제공(Provide)
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart'; // 방금 만든 모델 클래스
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: CounterScreen(),
);
}
}
class CounterScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Provider 상태 관리")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<CounterModel>(
builder: (context, counter, child) {
return Text(
"현재 카운트: ${counter.count}",
);
},
),
ElevatedButton(
onPressed: () {
context.read<CounterModel>().increment();
},
child: Text("카운트 증가"),
),
],
),
),
);
}
}
Consumer<CounterModel>
을 사용하여 count 값이 변경될 때마다 해당 부분만 다시 빌드됩니다..read()
메서드를 사용하고 있는데요, context.watch<T>()
는 상태 변경 시 UI를 리빌드하고, context.read<T>()
는 상태 변경을 감지하지 않고 값을 가져옵니다..watch()
메서드를 사용하면버튼 자체가 상태 변경을 감지하여 불필요하게 다시 빌드될 가능성이 있습니다. 이 메서드는 값(상태)을 감시해야 할 때만 써주세요!notifyListeners()
를 호출하지 않는다면 어떤 일이 일어날까요?notifyListeners()
를 주석 처리 해보겠습니다.class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
// notifyListeners(); 호출하지 않음
}
}
increment()
가 호출되어 _count 값이 증가해도 UI는 업데이트되지 않습니다.
notifyListeners()
는 ChangeNotifier에서 상태가 변경되었음을 위젯 트리에 알리는 역할을 합니다.- ChangeNotifierProvider를 사용하면 ChangeNotifier 객체를 위젯 트리에 제공할 수 있습니다.
notifyListeners()
는 상태 변경시 ChangeNotifier안의 listener 콜백들을 순차적으로 실행합니다.
(이 때에 화면도 리빌드 됩니다)