React로 치면 Redux, Recoil 같은 도구!
StatefulWidget의 setState로도 state 변수를 관리할 수 있지만, 다른 위젯에서 해당 state값을 사용하려면 위젯에서 위젯으로 콜백을 넘겨줘야 하고... 복잡...😵 그래서 보다 편리한 전역 상태관리를 위해 Provider를 이용할 수 있다. Bloc패턴, getX 등 다른 방법도 있지만 가장 쉽고 직관적인 Provider를 학습해 봄!
앱 하단에 컬러 선택 버튼을 만들고, Color 타입의 state 변수를 만들어서 provider로 이를 관리하며 테마 색을 바꿔 보았당. (+다크모드)
설치 명령어: flutter pub add provider
우선 lib 디렉터리 안에 provider 디렉터리를 따로 만들어 myProvider.dart 파일을 생성했다. state가 많아지면 provider 파일 또한 주제별로 분리하는 게 좋겠지만, 일단 연습용이기 때문에..!
그리고 myProvider.dart 파일을 열어 state 변수를 담아 놓을 클래스를 생성하는데, 이때 ChangeNotifier를 상속(extends
키워드 이용)받거나 믹스인으로 추가(with
키워드 이용)해야 함!
_color
라는 private 변수를 만들고, 외부에서 꺼내 써야 하니까 getter도 만들었다.
class ThemeColor with ChangeNotifier {
Color _color = Color.fromRGBO(80, 80, 80, 1);
Color get color => _color;
}
그리고 같은 클래스 내에 state 값을 변경하기 위한 함수를 정의해 준다.
void changeColor(Color color) {
_color = color;
notifyListeners();
}
tap에 따라 테마 색상을 바꿀 거기 때문에, 미리 Color 타입 변수를 만들어 테마 색상을 몇 가지 정의해 놓고 이용했다.
Color red = Color.fromRGBO(237, 115, 88, 1);
Color yellow = Color.fromRGBO(242, 220, 139, 1);
Color green = Color.fromRGBO(172, 201, 131, 1);
Color blue = Color.fromRGBO(134, 197, 207, 1);
Color purple = Color.fromRGBO(123, 113, 209, 1);
그렇게 완성된 myProvider.dart 파일
이제 해당 state를 사용할 파일(main.dart)로 넘어가서! 변수와 메서드를 사용할 수 있도록 세팅을 해 줘야 한다. 그 전에 provider패키지&myProvider 파일 임포트 하기~~
import 'package:provider/provider.dart';
import '../../provider/myProvider.dart';
원래 main.dart 내부의 기본 main() 함수는 아래처럼 runApp 메서드의 콜백으로 최상단 위젯(MyApp)을 전달하는 형태이다.
void main() {
runApp(MyHome());
}
provider를 사용하기 위해서는, 여기서 최상단 위젯인 MyApp을 MultiProvider로 감싸줘야 함! 사실 지금은 ChangeNotifierProvider로 전체를 감싸도 되지만... 상태 관리할 provider class가 두 개 이상이라면 멀티프로바이더를 사용해야 한다.
구체적으로 MultiProvider에는
1. 필수 프로퍼티인 providers에 ChageNotifierProvider<클래스명>(create: (_) => 클래스명())
의 형태로 필요한 ChangeNotifierProvider의 리스트를 전달해야 한다.
2. child에 최상단 위젯(MyApp())을 넣는다.
void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider<ThemeColor>(create: (_) => ThemeColor())
], child: MyApp()));
}
이제 MyApp() 내에서 ThemeColor내부의 변수 및 메서드를 사용할 수 있다!
state값이 변할 때마다 반영할 곳에는 context.watch<클래스명>().변수명
으로 state 변수를 넣어 준다. 나는 AppBar의 색이 되는 ThemeData의 primaryColor 프로퍼티 값으로 color를 적용해 줌!
Widget build(BuildContext context) {
return MaterialApp(
title: 'Let\'s Learn Flutter 📱',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: context.watch<ThemeColor>().color,
...
)
...
그리고 state값 조작이 필요한 곳에는 context.read<클래스명>().메서드명([인자])
형태로 onTap 등의 콜백으로 전달한다.
나는 컬러 수만큼 GesturedDetector 위젯으로 버튼을 만들어 줬는데, 반복을 줄이기 위해서 컬러 리스트를 정의하고 map함수를 이용해 다음과 같이 코드를 짰다.
class MyApp extends StatelessWidget {
final colorList = <Color>[
ThemeColor().red,
ThemeColor().yellow,
ThemeColor().green,
ThemeColor().blue,
ThemeColor().purple
];
...
persistentFooterButtons: colorList
.map<Widget>(
(color) => GestureDetector(
onTap: () {
context.read<ThemeColor>().changeColor(color);
},
child: Container(
width: 20,
height: 20,
decoration:
BoxDecoration(shape: BoxShape.circle, color: color),
),
),
)
.toList()
이번에 알게된 map 사용시 주의할 점!
children처럼 위젯 리스트가 필요한 곳에서 map을 쓰려면, .toList()
를 꼭 붙여줘야 한다. 맵의 결과는 리스트가 아닌 Iterable이기 때문에 타입 불일치 오류를 방지하려면 리스트 형태로 변환이 필요하기 때문에..!
그리고 map 뒤에도 <Widget>
이라고 타입을 명시해 주면 타입 추론으로 발생하는 오류도 방지할 수 있음!
플러터....... 새롭고 어렵지만 재밌당
https://dev-yakuza.posstree.com/ko/flutter/provider/
https://terry1213.github.io/flutter/flutter-provider/
https://dev.to/theotherdevs/starting-with-flutter-a-simple-guide-for-provider-247o