오늘은 ChangeNotifierProvider 에 대해서 정리했습니다.
개발환경 : 윈도우11, 안드로이드 스튜디오(Arctic Fox 2020.3.1 Patch 4), flutter 2.10
소스코드 위치 - Release setState_to_Provider · mike-bskim/provider_overview (github.com)
상태관리 패키지들도 다양하고 하나의 패키지 내에서도 다양한 방식으로 상태관리를 할 수 있습니다. 그중에서 오늘은 ChangeNotifierProvider 에 대해서 알아보겠습니다.
먼저 화면은 아래와 같습니다. 이전 setState 샘플 코드와 기본 구조는 같고 이해를 위해서 색상 및 구조를 약간 변경하였습니다.
기본 콘솔 로그는 아래와 같습니다. Middle 위젯에서 하는일이 없다보니, 버튼클릭시 호출순서에 상관없이 먼저 출력됩니다.
I/flutter ( 7675): -----------------------------------
I/flutter ( 7675): MyApp >> build
I/flutter ( 7675): ===================================
I/flutter ( 7675): MyHomePage >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterB >> build
I/flutter ( 7675): Sibling >> build
// 가독성을 위한 임의 줄바꿈
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): CounterB >> build
I/flutter ( 7675): Sibling >> build
// 가독성을 위한 임의 줄바꿈
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): CounterB >> build
I/flutter ( 7675): Sibling >> build
전체 소스 코드는 "더보기" 클릭하면 볼수 있습니다.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class Counter with ChangeNotifier {
String titleCounterA = 'Counter A';
String titleCounterB = 'Counter B';
String titleMiddle = 'Middle';
String titleSibling = 'Sibling';
int counter = 0;
void increment() {
counter++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint('-----------------------------------');
debugPrint('MyApp >> build');
return MaterialApp(
title: 'Counter',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
debugPrint('===================================');
debugPrint('MyHomePage >> build');
return Scaffold(
appBar: AppBar(
title: const Text('My Counter'),
),
body: ChangeNotifierProvider<Counter>(
create: (context) => Counter(),
child: Center(
child: Container(
color: Colors.grey,
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Text('MyHomePage', style: TextStyle(fontSize: 24.0)),
SizedBox(height: 20.0),
CounterA(),
SizedBox(height: 20.0),
Middle(),
],
),
),
),
),
);
}
}
class CounterA extends StatelessWidget {
const CounterA({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint('CounterA >> build');
return Container(
color: Colors.red[100],
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Text(Provider.of<Counter>(context).titleCounterA),
Text(
'${Provider.of<Counter>(context).counter}',
style: const TextStyle(fontSize: 48.0),
),
ElevatedButton(
onPressed: Provider.of<Counter>(context).increment,
child: const Text(
'Increment',
style: TextStyle(fontSize: 20.0),
),
),
],
),
);
}
}
class Middle extends StatelessWidget {
const Middle({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint('Middle >> build');
return Container(
color: Colors.grey[200],
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Text(Provider.of<Counter>(context).titleMiddle),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: const [
CounterB(),
SizedBox(width: 20.0),
Sibling(),
],
),
],
),
);
}
}
class CounterB extends StatelessWidget {
const CounterB({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint('CounterB >> build');
return Container(
color: Colors.yellow[100],
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Text(Provider.of<Counter>(context).titleCounterB),
Text(
// '${Provider.of<Counter>(context).counter}',
'${context.watch<Counter>().counter}',
style: const TextStyle(fontSize: 24.0),
),
],
),
);
}
}
class Sibling extends StatelessWidget {
const Sibling({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint('Sibling >> build');
return Container(
color: Colors.orange[100],
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Text(Provider.of<Counter>(context).titleSibling),
const Text(
'Sibling',
style: TextStyle(fontSize: 24.0),
),
],
),
);
}
}
모든 위젯이 다시 빌드되는것을 볼수 있습니다.
Provider 를 사용하더라도 구조에 따라서 특별한 성능차이가 없을수도 있다는 예시입니다.
그럼 여기서 몇가지 수정을 하여 효율적인 코드를 만들어 보겠습니다.
Sibling 위젯을 수정했습니다.
class Sibling extends StatelessWidget {
const Sibling({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint('Sibling >> build');
return Container(
color: Colors.orange[100],
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
// Text(Provider.of<Counter>(context).titleSibling),
Text(context.read<Counter>().titleSibling),
const Text(
'Sibling',
style: TextStyle(fontSize: 24.0),
),
],
),
);
}
}
위젯이 빌드되지 않는것을 볼수 있습니다.
또는 "Text(Provider.of<Counter>(context, listen: false).titleSibling)," listen을 false 처리해도 된다.
I/flutter ( 7675): ===================================
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): CounterB >> build
I/flutter ( 7675): ===================================
I/flutter ( 7675): Middle >> build
I/flutter ( 7675): CounterA >> build
I/flutter ( 7675): CounterB >> build
Middle 위젯도 동일하게 처리하리 할 수 있다.
'Flutter > 07 State - Provider' 카테고리의 다른 글
[Flutter] Provider Access - Named route (0) | 2022.05.06 |
---|---|
[Flutter] Provider Access - Anonymous route (0) | 2022.05.06 |
[Flutter] Could not find the correct Provider (0) | 2022.05.05 |
[Flutter] Consumer, Selector (0) | 2022.05.05 |
[Flutter] setState (0) | 2022.05.04 |