오늘은 StateNotifierProvider 에 대해서 알아보겠습니다.
개발환경 : 윈도우11, 안드로이드 스튜디오(Arctic Fox 2020.3.1 Patch 4), flutter 2.10
소스코드 - https://github.com/mike-bskim/state_notifier_test
기본기능은 아래 영상과 같습니다.
appBar 의 색상에 따라서 카운터값 증가분이 다릅니다. 파랑(1씩 증가), 검정(10씩 증가), 빨강(10씩 감소).
카운터값이 ~50까진 body 배경색이 흰색, 51-99는 회색, 100이상 노란색.
프로젝트 구조는
bg_color.dart
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:state_notifier/state_notifier.dart';
class BgColorState extends Equatable {
final Color color;
const BgColorState({
required this.color,
});
@override
List<Object> get props => [color];
@override
bool get stringify => true;
BgColorState copyWith({
Color? color,
}) {
return BgColorState(
color: color ?? this.color,
);
}
}
class BgColor extends StateNotifier<BgColorState> {
BgColor() : super(const BgColorState(color: Colors.blue));
void changeColor() {
// state 변수는 super 전달하는 값이 초기값이다.
// notifyListeners 호출 안함.
if (state.color == Colors.blue) {
state = state.copyWith(color: Colors.black);
} else if (state.color == Colors.black) {
state = state.copyWith(color: Colors.red);
} else {
state = state.copyWith(color: Colors.blue);
}
}
}
counter.dart - BgColoerState.color 에 의존하는 provider
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:state_notifier/state_notifier.dart';
import 'bg_color.dart';
class CounterState extends Equatable {
final int counter;
const CounterState({
required this.counter,
});
@override
List<Object> get props => [counter];
@override
bool get stringify => true;
CounterState copyWith({
int? counter,
}) {
return CounterState(
counter: counter ?? this.counter,
);
}
}
// Counter 는 BgColor 의 state 를 읽어야 하므로
// ProxyProvider 대신 LocatorMixin 를 사용함.
class Counter extends StateNotifier<CounterState> with LocatorMixin {
Counter() : super(const CounterState(counter: 0));
void increment() {
Color currentColor = read<BgColor>().state.color;
// debugPrint('-- counter>>increment: ' + currentColor.toString());
// appBar 의 배경색에 따라서 값 증분이 달라짐.
if (currentColor == Colors.black) {
state = state.copyWith(counter: state.counter + 10);
} else if (currentColor == Colors.red) {
state = state.copyWith(counter: state.counter - 10);
} else {
state = state.copyWith(counter: state.counter + 1);
}
}
// 다른 object의 변경사항을 listen 할수 있음,
// update 는 의존하는 다른 state 가 변경될때 마다 호출된다.
// ProxyProvider 가 다른 provider 에 하는것과 동일한 기능
@override
void update(Locator watch) {
// debugPrint('----- counter>>update(BgColor): nothing');
debugPrint('----- counter>>update(BgColor): ${watch<BgColorState>().color.toString()}');
debugPrint('----- counter>>update(BgColor): ${watch<BgColor>().state.color.toString()}');
super.update(watch);
}
}
customer_level.dart - CounterState.count 에 의존하는 provider
import 'package:state_notifier/state_notifier.dart';
import 'counter.dart';
enum Level {
bronze,
silver,
gold,
}
class CustomerLevel extends StateNotifier<Level> with LocatorMixin {
CustomerLevel() : super(Level.bronze);
// update 는 의존하는 다른 state 가 변경될때 마다 호출된다.
// 여기서는 CounterState 의 counter 가 변경될때 마다 호출됨.
@override
void update(Locator watch) {
final currentCounter = watch<CounterState>().counter;
// debugPrint('----- level>>update(counter): ${currentCounter.toString()}');
// debugPrint('----- level>>update(bgColor): ${watch<BgColorState>().color.toString()}');
if (currentCounter >= 100) {
state = Level.gold;
} else if (currentCounter > 50 && currentCounter < 100) {
state = Level.silver;
} else {
state = Level.bronze;
}
super.update(watch);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
import 'package:provider/provider.dart';
import 'providers/bg_color.dart';
import 'providers/counter.dart';
import 'providers/customer_level.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// ProxyProvider 와 마찬가지로 순서가 바뀌면 오류가 난다.
// 영향을 주는 provider 는 영향을 받는 provider 보다 먼저 표기해야 함
StateNotifierProvider<BgColor, BgColorState>(
create: (context) => BgColor(),
),
StateNotifierProvider<Counter, CounterState>(
create: (context) => Counter(),
),
StateNotifierProvider<CustomerLevel, Level>(
create: (context) => CustomerLevel(),
),
],
child: MaterialApp(
title: 'StateNotifier',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final colorState = context.watch<BgColorState>();
final counterState = context.watch<CounterState>();
final levelState = context.watch<Level>();
return Scaffold(
backgroundColor: levelState == Level.bronze
? Colors.white
: levelState == Level.silver
? Colors.grey
: Colors.yellow,
appBar: AppBar(
backgroundColor: colorState.color,
title: const Text('StateNotifier'),
),
body: Center(
child: Text(
'${counterState.counter}',
style: Theme.of(context).textTheme.headline2,
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
tooltip: 'Increment',
child: const Icon(Icons.add),
onPressed: () {
context.read<Counter>().increment();
},
),
const SizedBox(width: 10.0),
FloatingActionButton(
tooltip: 'Change Color',
child: const Icon(Icons.color_lens_outlined),
onPressed: () {
context.read<BgColor>().changeColor();
},
),
],
),
);
}
}
[참고자료] udemy - Flutter Provider Essential 코스 (Korean)
'Flutter > 07 State - Provider' 카테고리의 다른 글
[Flutter] Provider 상태에 따른 추가 액션(with addListener) (0) | 2022.05.23 |
---|---|
[Flutter] Provider 상태에 따른 추가 액션(without addListener) 오류 (0) | 2022.05.09 |
[Flutter] Provider 상태에 따른 추가 액션(without addListener) (0) | 2022.05.09 |
[Flutter] Provider - errors(Provider with Navigator.push) (0) | 2022.05.08 |
[Flutter] Provider - errors(provider with Dialog) (0) | 2022.05.08 |