상태 = 데이터 = 변수
변수를 수정하면 알아서 UI도 바뀌게 하자!
정해진 룰은 없다. 대부분의 경우 setState()만으로 충분하다. 하지만 비동기 처리(네트워크 통신 등의 오래 걸리는 처리)가 많으면 다른 상태관리 도구를 선택하는 것이 좋다.
상태관리를 편하게 하기 위한 라이브러리들 : Bloc, Provider, RiverPod, GetX 등
// Logic
import 'package:flutter/material.dart';
class CounterModel extends ChangeNotifier {
int count = 0;
void increase() {
count++;
notifyListeners();
}
}
Provider 의존성 추가
flutter pub add provider
제공할 위젯 트리의 최상단에 ChangeNotifierProvider 위젯 배치
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: const MyApp(),
),
);
}
body: Center(
child: Consumer<CounterModel>(
builder: (context, model, child) {
return Text(
'${model.count}',
style: const TextStyle(fontSize: 40),
);
},
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
context.read<CounterModel>().increase();
},
),
Provider.of<CounterModel>(context, listen: false).increase();
와 동일한 코드이다.(예전 방식)
build(BuildContext context) {
final model = context.watch<CounterModel>();
}
Widget
body: Center(
child: Text(
'${model.count}',
style: const TextStyle(fontSize: 40),
);
),
제공할 객체 준비
class HomeViewModel {
final counter = Counter();
final _streamController = StreamController<int>();
int get count => counter.count;
Stream<int> get countStream => _streamController.stream;
void increase() {
counter.increase();
_streamController.add(counter.count);
}
}
제공할 위젯 트리의 최상단에 Provider 위젯 작성
Stream을 안 쓰는 경우 Provider보다 ChangeNotifierProvider를 주로 사용한다. 상태 변경을 자동으로 모니터링 한다,
build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Provider(
create: (_) => HomeViewModel(),
child: const MyHomePage(),
),
);
}
Widget
사용할 때는 Provider.of(context)로 객체를 얻는다.
확장함수 context.watch()와 동일
build(BuildContext context) {
final viewModel = Provider.of<HomeViewModel>(context);
Widget
변경사항이 있을 객체에 ChangeNotifier 구현
- class HomeViewModel {
+ class HomeViewModel with ChangeNotifier {
final counter = Counter();
void increase() {
counter.increase();
- _streamController.add(counter.count);
+ notifyListeners();
- home: Provider(
+ home: ChangeNotifierProvider(
create: (_) => HomeViewModel(),
child: const MyHomePage(),
),
- StreamBuilder<int>(
stream: viewModel.countStream,
builder: (context, snapshot) {
return Text(
'${viewModel.count}',
style: Theme.of(context).textTheme.headline4,
);
}),
+ Text(
'${viewModel.count}',
style: Theme.of(context).textTheme.headline4,
),
StreamBuilder와 유사한 느낌, Consumer를 통해서 ViewModel을 직접 접근
build(BuildContext context) {
- final viewModel = Provider.of<HomeViewModel>(context);
return Scaffold(
appBar: AppBar(
title: const Text('title'),
),
...
const Text(
'You have pushed the button this many times: ',
),
- Text(
'${viewModel.count}',
style: Theme.of(context).textTheme.headline4,
),
+ Consumer<HomeViewModel>(
builder: (_, viewModel, child) {
return Text(
'${viewModel.count}',
style: Theme.of(context).textTheme.headline4,
);
},
),
}
Widget
Provider
Provider는 consumer를 이용해 상태값을 가져오고 provider와 context를 이용해 controller의 비즈니스 로직을 불러온다. provider의 경우, stateful 위젯으로 변경하거나 context를 넘겨줘야한다.
controller
class CountControllerWithProvider extends ChangeNotifier {
int count = 0;
void increase() {
count++;
notifyListeners();
}
}
ui
class WithProvider extends StatelessWidget {
const WithProvider({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Text(
"Provider",
style: TextStyle(fontSize: 50),
),
Consumer<CountControllerWithProvider>(
builder: (_, snapshot, child) {
return Text("${snapshot.count}", style: TextStyle(fontSize: 50));
},
),
RaisedButton(
onPressed: () {
// listen: false를 줘서 consumer 부분만 rebuild
Provider.of<CountControllerWithProvider>(context, listen: false)
.increase();
},
child: Text(
"+",
style: TextStyle(fontSize: 50),
),
),
],
),
);
}
}
GetX
GetX는 GetBuilder를 통해 상태값을 가져오고 Get.find를 통해 controller의 비즈니스 로직을 가져온다. context를 사용하지 않는 GetX 방식은 위젯으로 분리하여 사용 가능하다. 부모에서 controller를 선언해줄 필요없이 ui 부분에서 바로 사용 가능하다. 또한 클래스가 생성될때 바로 사용가능하다.
controller
class CountControllerWithGetX extends GetxController {
int count = 0;
void increase() {
count++;
update();
}
}
ui
class WithGetX extends StatelessWidget {
const WithGetX({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"GetX",
style: TextStyle(fontSize: 50),
),
GetBuilder<CountControllerWithGetX>(builder: (controller) {
return Text(
"${controller.count}",
style: TextStyle(fontSize: 50),
);
}),
RaisedButton(
onPressed: () {
Get.find<CountControllerWithGetX>().increase();
},
child: Text(
"+",
style: TextStyle(fontSize: 50),
),
),
],
),
);
}
}