CH02 Provider Overview



necessity of provider




Widget A ์์ counter ๋ผ๋ ๋ฐ์ดํฐ์ increment ๋ผ๋ ํจ์๊ฐ ํ์ํ ์ํฉ์ด๊ณ , Widget B ์์๋ counter ๋ผ๋ ๋ฐ์ดํฐ๋ง ํ์ํ ์ํฉ.
Widget A, Widget B ๋ชจ๋์์ ๋์ผํ ๋ฐ์ดํฐ์ธ counter ๊ฐ ํ์ํ๋ฏ๋ก ๊ณตํต๋ ๋ถ๋ชจ Widget C ์์ ์ ์.
counter ์ increment ์ ๊ฒฝ์ฐ Widget C ์์ ์ ์ธํ์ง๋ง ์ ์ ์ ์ด๋ Widget A ์์ ํ๋ฏ๋ก Inversion of control ๋ฐ์.
Widget B ์ counter ๋ฅผ ๋๊ฒจ์ฃผ๊ธฐ ์ํด์ Widget C ์ Widget B ์ฌ์ด์ Widget ์ด ํ์ํ์ง๋ ์์ counter ๋ผ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ฒ ๋จ.
managing state without provider

Counter B ๋ง ๋ด์๋ ์ด๋์ rebuilding ์ด ๋ฐ์ํ๋์ง ์ถ์ ํ๊ธฐ๊ฐ ์ฝ์ง ์๋ค.
๊ณตํต ์๋จ ๋ถ๋ชจ Widget ์์ setState() ํธ์ถ๋๋ฏ๋ก ์ธ๋ฐ์์ด ๋ ๋ง์ Widget ์ด rebuild ๋์์ด ๋์ด ํผํฌ๋จผ์ค๊ฐ ๋จ์ด์ง ์ ์๋ค.


๊ฒฐ๊ตญ StateManagement ๋ ์๋ ๋ ๊ฐ์ง ํ์๊ฐ ํต์ฌ์ด๋ค.
Dependency Injection (Object๋ฅผ Widget Tree ์์์ ์ฝ๊ฒ ์ ๊ทผํ ์ ์๋๋ก ํ๋ค.)
Synchronizing data and UI


Provider ๋ Widget ์ Widget ์ด ์๋ ๋ฐ์ดํฐ์ method ์ ์ฝ๊ฒ ์ ๊ทผํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ ๋์์ ๋ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์๋ค๋ ์ฌ์ค์ ๊ทธ ๋ฐ์ดํฐ๊ฐ ํ์ํ Widget ์ ์ ๊ณตํด์ ํ์์ rebuild ๋ ์ ์๋๋ก ํ๋ค.
๊ฒฐ๊ตญ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ํ๋ฉด์ด ๋ถ๋ฆฌ ๋๋ ๊ฒ์ด๋ค.
dependency injection using provider

์ฃผ๋ชฉํด์ผํ ํฌ์ธํธ๋ ์๋ ๋ ๊ฐ์ง์ด๋ค.
Widget Tree ์์์ class Dog ๋ก์ ์ ๊ทผ์ด ์ผ๋ง๋ ์ฝ๊ฒ ๊ฐ๋ฅํ์ง
์ด ๋ฐฉ์์ด ์์ฑ์๋ก ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ฃผ๋ ๊ฒ๋ณด๋ค ์ผ๋ง๋ ๋ ๊ฐํธํ์ง

Provider ์ญ์ Widget ์ด๋ค.
create ํ๋กํผํฐ์์ Widget ์ด ํ์๋ก ํ๋ dog ์ธ์คํด์ค๋ฅผ ๋ง๋ ๋ค.
create ์ assign ๋๋ ํจ์๊ฐ return ํ๋ object ์ Provider ํ์ Widget ๋ค์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
Provider ์ static ํจ์์ธ of ๋ฅผ ์ด์ฉํ๋ฉด Widget Tree ๋ฅผ ์๋ก traverse ํ๋ฉด์ ์ํ๋ Type ์ ์ธ์คํด์ค๋ฅผ ์ฐพ์ ์ ์๋ค.
Provider.of<Dog>(context)์ ๊ฐ์ ์์ธ๋ฐ, ์ฌ๊ธฐ์ context ๋ฅผ ์ฃผ๋ ์ด์ ๋ context ๋ฅผ ์ด์ฉํด์ Widget Tree ๋ฅผ ์๋ก ํ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.

<T>๊ฐ ๊ฐ์ ๋ ๊ฐ ์ด์์ ์ธ์คํด์ค๊ฐ ์๋ ๊ฒฝ์ฐ์๋ ๊ฐ์ฅ ๊ฐ๊น์ด ์ธ์คํด์ค๋ฅผ ๊ฐ์ ธ์จ๋ค.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Provider<Dog>(
create: (context) => Dog(
name: 'Sun',
breed: 'Bulldog',
age: 3,
),
child: MaterialApp(
title: 'Provider 02',
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) {
return Scaffold(
appBar: AppBar(
title: Text('Provider 02'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'- name: ${Provider.of<Dog>(context).name}',
style: TextStyle(fontSize: 20.0),
),
SizedBox(height: 10.0),
BreedAndAge(),
],
),
),
);
}
}
ChangeNotifier & addListener


addListener ๋ ์๋์ผ๋ก dispose ๋์ง ์๊ธฐ ๋๋ฌธ์ ์๋์ผ๋ก ์ง์ dispose ์์ผ์ค์ผํ๋ค.
์ฝ๋ ๋ด Provider ์ ๊ฑฐ ํ ChangeNotifier ์ addListener ๋ฅผ ๊ฐ์ด ์ฐ๋ฉด์ Listener ๊ฐ ์๋ํ๋ ๊ฒ์ ๋ณด๊ณ , ๋ถํธํ ์ ์ ์ดํด๋ณด์๋ค.(Provider ์ญ์ ํ ์์ฑ์๋ก ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ฃผ์๋ค.)
ChangeNotifierProvider


Provider.of<T>(context)๋ก ์ธ์คํด์ค๋ฅผ ํ์ & ์ ๊ทผ์ ํด๋น ๋ฐ์ดํฐ์ ๋ณ๊ฒฝ์ listen ํด์ผํ ์ง์ ํ์์ฑ(๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ๋ฐ๋ฅธ UI rebuild)์ด ์์๋์ ์์๋ ๊ฐ๊ฐ ์ต์ ๊ฐ์ด ๋ค๋ฆ์ ์ธ์งํ๋ค.Provider.of<Dog>(context).ageProvider.of<Dog>(context, listen: false).age
๊ฒฐ๋ก ์ ์ผ๋ก ์๋์ ๊ฐ๋ค. ๊ฒฐ๊ตญ ์๋ ๋ ๊ฐ์ง๊ฐ State Management ์ด๋ค.
ChangeNotifierProvider ๋ ๋ฐ์ดํฐ๋ฅผ ํ์๋ก ํ๋ Widget ์ด dependency injection ์ ๋ฐ์์ผ๋ก์จ ๋ฐ์ดํฐ(์ธ์คํด์ค)์ ์ฝ๊ฒ ์ ๊ทผํ ์ ์๊ฒ ํด์ค๋ค.
๋ฐ์ดํฐ์ ๋ณ๊ฒฝ์ด ๋ฐ์ํ์ ๋ ์ด ๋ฐ์ดํฐ์ ๋ณํ์ ๋ง์ถฐ์ ์ ํ์ ์ผ๋ก Widget ์ rebuild ํ ์ ์๊ฒ ํด์ค๋ค.
import 'package:flutter/foundation.dart';
class Dog with ChangeNotifier {
final String name;
final String breed;
int age;
Dog({
required this.name,
required this.breed,
this.age = 1,
});
void grow() {
age++;
notifyListeners();
}
}ChangeNotifier ์ notifyListeners() ๋ ์ด๋ฅผ ๊ตฌ๋ ํ๊ณ ์๋ ๋ชจ๋ listener ๋ค์๊ฒ ๋ณ๊ฒฝ ์ฌ์ค์ ์๋ฆฌ๊ณ rebuild ํ๋๋ก ๋ง๋ ๋ค. ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/dog.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Dog>(
create: (context) => Dog(name: 'dog04', breed: 'breed04'),
child: MaterialApp(
title: 'Provider 04',
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) {
return Scaffold(
appBar: AppBar(
title: Text('Provider 04'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'- name: ${Provider.of<Dog>(context).name}',
style: TextStyle(fontSize: 20.0),
),
SizedBox(height: 10.0),
BreedAndAge(),
],
),
),
);
}
}
class BreedAndAge extends StatelessWidget {
const BreedAndAge({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'- breed: ${Provider.of<Dog>(context).breed}',
style: TextStyle(fontSize: 20.0),
),
SizedBox(height: 10.0),
Age(),
],
);
}
}
class Age extends StatelessWidget {
const Age({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'- age: ${Provider.of<Dog>(context).age}',
style: TextStyle(fontSize: 20.0),
),
SizedBox(height: 20.0),
ElevatedButton(
onPressed: () => Provider.of<Dog>(context, listen: false).grow(),
child: Text(
'Grow',
style: TextStyle(fontSize: 20.0),
),
),
],
);
}
}
read, watch, select extension methods

4.1 ๋ถํฐ Provider ๋ฅผ ๋ ๊ฐํธํ๊ฒ ์ธ ์ ์๊ฒ ํด์ฃผ๋ extension ์ด ๋์ ๋จ.
์ผ์ข ์ shortCut ์ด๋ผ๊ณ ๋ณด๋ฉด ๋๊ณ , ๊ทธ๋ฅ ์ฐ์ง ๋ง๊ณ ์ํ๋ค์ด ๋ญ์ง ์ดํดํ๊ณ ์๋๊ฒ ์ค์ํ๋ค.
context.select๋ ๋ค์์ property ๋ฅผ ๊ฐ์ง๊ณ ์๋ object ์ ํน์ property ์ ๋ณํ๋ง listen ํ๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ค.context.watch๋ ํน์ property ํ๋๋ง ๋ฐ๋์ด๋ rebuild ๋ฅผ ํ๋ ๊ฒ์ ๋ฐํดcontext.select๋ listen ํ๊ณ ์ถ์ ๊ฒ๋ง ์ ๋ณ์ ์ผ๋ก listen ์ด ๊ฐ๋ฅํ๋ค.(ํผํฌ๋จผ์ค ๊ณ ๋ ค ์ธก๋ฉด์์ ์ฌ์ฉํ ์ ์๋ค.)
context.watch vs context.select
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/dog.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Dog>(
create: (context) => Dog(name: 'dog05', breed: 'breed05', age: 3),
child: MaterialApp(
title: 'Provider 05',
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) {
return Scaffold(
appBar: AppBar(
title: Text('Provider 05'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'- name: ${context.watch<Dog>().name}',
style: TextStyle(fontSize: 20.0),
),
SizedBox(height: 10.0),
BreedAndAge(),
],
),
),
);
}
}
class BreedAndAge extends StatelessWidget {
const BreedAndAge({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'- breed: ${context.select<Dog, String>((Dog dog) => dog.breed)}',
style: TextStyle(fontSize: 20.0),
),
SizedBox(height: 10.0),
Age(),
],
);
}
}
class Age extends StatelessWidget {
const Age({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'- age: ${context.select<Dog, int>((Dog dog) => dog.age)}',
style: TextStyle(fontSize: 20.0),
),
SizedBox(height: 20.0),
ElevatedButton(
onPressed: () => context.read<Dog>().grow(),
child: Text(
'Grow',
style: TextStyle(fontSize: 20.0),
),
),
],
);
}
}
Multiple Provider

ํ๋์ Provider ๋ฅผ ์ฌ์ฉํ๋๋ผ๋ MultiProvider ๋ฅผ ์ฌ์ฉํด๋์ผ๋ฉด ํ์ฅ์ฑ์ด ์๋ค.
Future Provider

๊ฐ์ฌ๋์ ๊ฐ์ธ์ ์ผ๋ก ์ธ ์ผ์ด ๊ฑฐ์ ์์๋ค๊ณ ํจ.
๋ง์ฝ ์ธ ์ผ์ด ์์ผ๋ฉด FutureBuilder ๋ฅผ ์ธ ๊ฒ ๊ฐ๋ค๊ณ ํจ.
Widget Tree ์๋ ๋น๋๊ฐ ๋์๋๋ฐ ์ฌ์ฉํ๊ณ ์ ํ๋ ๊ฐ์ด ์์ง ์ค๋น๊ฐ ๋์ง ์์์๋ ์ฌ์ฉํ๋ค.
Future ๊ฐ resolve ๋์ง ์์์๋, initialData ๋ก build ํ๊ณ Future ๊ฐ resolve ๋๋ฉด rebuild ๋๋ค. ์ด 2๋ฒ build ๊ฐ ๋๋ค๋ ๊ฒ. (๋ง์ฝ ์ฌ๋ฌ๋ฒ build ๋ฅผ ์ํ๋ค๋ฉด StreamProvider ์ฌ์ฉํ๋ค.)
class Babies {
final int age;
Babies({
required this.age,
});
Future<int> getBabies() async {
await Future.delayed(Duration(seconds: 3));
if (age > 1 && age < 5) {
return 4;
} else if (age <= 1) {
return 0;
} else {
return 2;
}
}
}class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<Dog>(
create: (context) => Dog(name: 'dog06', breed: 'breed06', age: 3),
),
FutureProvider<int>(
initialData: 0,
create: (context) {
final int dogAge = context.read<Dog>().age;
final babies = Babies(age: dogAge);
return babies.getBabies();
},
),
],
child: MaterialApp(
title: 'Provider 06',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
),
);
}
}์ ์ฌํ ๋ด๋ฌ์ผํ ๋ถ๋ถ์ FutureProvider ์ ๋์์ Future ์ด๊ธฐ๋ง ํ๋ฉด ๋๋ ๊ฒ์ด๋ผ๋ ๊ฒ์ด๋ค. ์ฆ, ์ธ์คํด์ค ๋ด์ ์ ์๋ method ๋ฅผ ๋์์ผ๋ก ํ๊ณ ์ถ๋ค๋ฉด ์ธ์คํด์ค ์ ์ฒด๋ฅผ return ํ ํ์๊ฐ ์๊ณ ํน์ ๋ฉ์๋๋ง ์ ๊ณตํ๋ ํํ๋ก ์ฌ์ฉํด์ผ ํ๋ค.
FutureProvider ๋ด์์ context.read ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค. ์ด์ ๋ ์์์ ChangeNotifierProvider ๊ฐ FutureProvider ๋ณด๋ค ์์ Widget ์ผ๋ก ์ด๋ฏธ ์ฌ์ฉ๋์๊ธฐ ๋๋ฌธ์ด๋ค.
StreamProvider

๊ฐ์ฌ๋ ๊ฐ์ธ์ ์ผ๋ก๋ FutureProvider ๋ณด๋ค StreamProvider ๋ฅผ ์ฌ์ฉํ ์ผ์ด ๋ ๋ง์๋ค๊ณ ํจ.
์ฐ์๋๋ Future value ๋ฅผ ํ๊ฒ์ผ๋ก ํ๋ Provider
StreamProvider<String>(
initialData: 'Bark 0 times',
create: (context) {
final int dogAge = context.read<Dog>().age;
final babies = Babies(age: dogAge * 2);
return babies.bark();
},
),create ๋ ํ ๋ฒ๋ง called ๋๊ธฐ ๋๋ฌธ์ watch ๋ฅผ ์ฌ์ฉํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๋ ผ๋ฆฌ์ ์ผ๋ก๋ read ๊ฐ ๋ง๋ค.
class Babies {
final int age;
Babies({
required this.age,
});
Future<int> getBabies() async {
await Future.delayed(Duration(seconds: 3));
if (age > 1 && age < 5) {
return 4;
} else if (age <= 1) {
return 0;
} else {
return 2;
}
}
Stream<String> bark() async* {
for (int i = 1; i < age; i++) {
await Future.delayed(Duration(seconds: 2));
yield 'Bark $i times';
}
}
}async ์ async* ์ ์ฐจ์ด๋ ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ๋ค. ๊ฐ๋ตํ ์ค๋ช ํ๋ฉด return ํ์ ์ด
Stream<T>๋ฅผ ์์ฐํ๋ฉด(+ method ๋ฅผ ๋ ๋์ง ์์ผ๋ฉด์) async* ๋ฅผ ์ฌ์ฉํ๋ค.์ด๋ฐ ๊ฒฝ์ฐ return ์ ์ฌ์ฉํ์ง ์๋๋ค. method ๋ฅผ ๋ ๋์ง ์๊ธฐ ๋๋ฌธ์ ์ข ๊ฒฐ์ฒ๋ฆฌ ํ๋ฉด ์๋๋ค. yield ๋ฅผ ์ฌ์ฉํ๋ฉฐ, yield ์ ์ฌ์ ์ ์๋ฏธ๋ โ์์ฐํ๋คโ ๋ผ๋ ๋ป์ด๋ค.
Consumer


class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Provider 08'),
),
body: Consumer<Dog>(
builder: (BuildContext context, Dog dog, Widget? child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
child!,
SizedBox(height: 10.0),
Text(
'- name: ${dog.name}',
style: TextStyle(fontSize: 20.0),
),
SizedBox(height: 10.0),
BreedAndAge(),
],
),
);
},
child: Text(
'I like dogs very much',
style: TextStyle(fontSize: 20.0),
),
),
);
}
}Consumer ์ ํ๋ผ๋ฏธํฐ (BuildContext context, Dog dog, Widget? child) ์ค child ๋ builder ๋ด์์ rebuild ๋ ํ์๊ฐ ์๋ Widget ์ด ์๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด์ ์์ ๊ฐ์ด ์ฌ์ฉํ๋ค. ์ ์์ ์์๋ ์ด๋ค ๊ฒฝ์ฐ๋ rebuild ๋ ํ์๊ฐ ์๋ Text Widget ์ child ๋ก ๋นผ์ฃผ์๋ค.
์ดํด๊ฐ ์๊ฐ๋ฉด ์ ํฌ๋ธ์ ๋จ์์๋ ๊ฐ์ ์์ ์ด ๋ถ๋ถ์ ๋ค์ ๋ณด๊ณ ์ค์.
Consumer, builder, ProviderNotFoundException






Consumer ๋ฅผ ์ฐ๋ builder ๋ฅผ ์ฐ๋ ๋ ์ค ํธํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์.
Selector

Consumer ์ ์ ์ฌํ๋ฐ Consumer ๋ณด๋ค ๋ ์ธ์ธํ ์ปจํธ๋กค์ ๊ฐ๋ฅํ๊ฒ ํด์ค๋ค.
์์์ ํ์ตํ
context.select<T, R>((R selector(T value))) => R๊ณผ ์ ์ฌํ ๊ฐ๋ ์ด๋ค.์ง๊ธ ํ์ตํ Selector ๋ผ๋ Widget ๊ณผ
context.select<T, R>((R selector(T value))) => R๋ ๊ณตํต์ ์ผ๋ก ํน์ property ์ ๋ณ๊ฒฝ์ ๋ํด์ react ํ ์ ์๋๋ก ํ๋ ๊ฒ.
ProviderNotFoundException ๋ ์์๋ณด๊ธฐ, Builder Widget

extension ์ context ๋ build method ์ buildContext ์์ ๋ช ์ฌ. ๊ทธ๋์ ๊ทธ๊ฑธ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ProviderNotFoundException ๊ฐ ๋ฐ์ํ ์ ๋ฐ์ ์๋ค.
์ด๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด โConsumer, builder, ProviderNotFoundExceptionโ ์์ ํ์ตํ builder ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ child ๋ก Widget ์ ๋ฐ๋ก ๋นผ์ ๋ณ๋์ Widget ์ผ๋ก ์ฌ์ฉํ๋ค.
Builder Widget ์ context ๋ ์กฐ์ Widget ์ BuildContext ๊ฐ ์๋๊ณ Builder Widget ์์ฒด์ BuildContext ์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์ฌ์ฉํด์ Widget Tree ๋ฅผ ์๋ก ํ์ํ๋ฉด ์ํ๋ T(type)์ ๋ํ Provider ๋ฅผ ์ฐพ์ ์ ์๋ค.
builder ํ๋กํผํฐ๋ ์ด๋ฏธ ํ์ตํ ๋ฐ์ ๊ฐ์ด syntax sugar ๋ก Builder Widget ์ด ์ฌ์ฉ๋ ๊ฒ๊ณผ ๋์ผํ๋ค.
Provider Access - Anonymous route access




Consumer, builder, ProviderNotFoundException์์ Error Message 2 ์ ๊ฒฝ์ฐ๋ฅผ ํ์ตํจ.Consumer, builder, ProviderNotFoundException์์ Error Message 3 ์ ๊ฒฝ์ฐ parent-child ๊ด๊ณ์์ child ๋ก์ Widget Tree ๋ฅผ ์๋ก ํ์ํ๋ฉด์ Provider ๋ฅผ ์ฐพ์ํ๋๋ฐ ํ์์ ์ฌ์ฉํ๋ context ๊ฐ ํด๋น child ์ context ๊ฐ ์๋๋ผ, Provider ์์น์ ๊ฐ๊ฑฐ๋ ํน์ ๊ทธ ์ด์์ level ์ context ์ผ ๊ฒฝ์ฐ ๋ฐ์ํ ์๋ฌ.Error Message 2 ๋ parent-child ๊ด๊ณ์์ child ๋ก์ ์๋ก ํ์์ด ๋ฐ์๋์ด์ผ ํ๋๋ฐ, ๋ค๋ฅธ route ์์ ํ์ํ์ผ๋ฏ๋ก(์ฌ๊ธฐ์ ์์ ๋ sibling ๊ด๊ณ) ๋ชป์ฐพ๋ ๋ฌธ์ ๊ฐ ๋ฐ์.
์ด ๋์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ value constructor ์ด๋ค. ์๋ก์ด sub-tree ์ ์ด๋ฏธ ์กด์ฌํ๋ class ์ ๋ํ access ๋ฅผ ์ ๊ณตํ ๋ ์ฌ์ฉ. value constructor ๋ class ๋ฅผ ์๋์ผ๋ก close ํ์ง ์๋๋ค.
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
return ChangeNotifierProvider.value(
value: context.read<Counter>(),
child: ShowMeCounter(),
);
}),
);
},context.read ์ ์ฌ์ฉํ๋ context ๋ Navigator.push ์ context ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. MaterialPageRoute ์ context ๊ฐ ์๋์ ๋ช ์ฌ. ์๋ก ํ์ํ์ฌ Provider ๋ฅผ ์ฐพ์์ผ ํ๋๋ฐ, MaterialPageRoute ์ ๊ฒฝ์ฐ sibling ์ด๊ธฐ ๋๋ฌธ์ MaterialPageRoute ์ context ๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ Provider ๋ฅผ ์ฌ์ ํ ๋ชป์ฐพ๋๋ค.
์๋๋ ์ฝ๋ ์ ์ฒด.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart';
import 'show_me_counter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Anonymous Route',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider<Counter>(
create: (context) => Counter(),
child: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: Text(
'Show Me Counter',
style: TextStyle(fontSize: 20.0),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
return ChangeNotifierProvider.value(
value: context.read<Counter>(),
child: ShowMeCounter(),
);
}),
);
},
),
SizedBox(height: 20.0),
ElevatedButton(
child: Text(
'Increment Counter',
style: TextStyle(fontSize: 20.0),
),
onPressed: () {
context.read<Counter>().increment();
},
)
],
),
),
);
}
}
Provider Access - Named route access
๊ณ์ํด์ Provider Access ์ ๋ํด์ ํ์ตํ๋ค. ๋ฐ๋ก ์์์๋ Anonymous route access ๋ฅผ ์ดํด๋ณด์๋ค. ์ ์์ ์ผ์ด์ค๊ฐ Anonymous route ๋๋ฉด Navigator.push ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด๋ฒ์๋ Named route ์์์ Provider ์ ๋ํ access ๋ฅผ ํ์ตํ๋ค.
๋ณต์ต ์ฐจ์์์ ์ง๊ณ ๋์ด๊ฐ์๋ฉด, Anonymous route ๊ฐ Anonymous ์ธ ์ด์ ๋ ๋ง ๊ทธ๋๋ก ์ด๋ฆ์ด ์์ด์๋ค. ๋ฐ๋๋ก ์ด๋ฆ์ด ์๋ค๋ ๋ง์ MaterialApp ์์ routes property ๋ด์ route ์ฃผ์์ Screen ์ผ๋ก ์ฌ์ฉํ Widget ์ด ๋ฑ๋ก๋์ด ์๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค. ๋ฐ๋ก ์ Anonymous route ๋ Navigator.push ๋ก route ์ค์ ์ ๋ฐ๋ก ํด์ฃผ์ง ์๊ณ ํ๋ฉด Stack ์ ๋ฐ๋ก ์์ ์ฌ๋ฆฐ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ Anonymous ์๋ค.
routes: {
'/': (context) => ChangeNotifierProvider.value(
value: _counter,
child: MyHomePage(),
),
'/counter': (context) => ChangeNotifierProvider.value(
value: _counter,
child: ShowMeCounter(),
),
},dart์์ ๊ฐ์ด routes ์ค์ ๋ถ๋ถ์์ ChangeNotifierProvider.value ๋ก ๊ฐ์ธ์ฃผ๋ฉด ๋๋๋ฐ, ์ฃผ์ํ ์ ์ด ์๋ค. create ๋ฅผ ์ฌ์ฉํด์ ์ฌ์ฉํ ์ธ์คํด์ค๋ฅผ ์์ฑํ ๊ฒฝ์ฐ ์๋์ผ๋ก dispose ๋ฅผ ํด์ฃผ๋๋ฐ ์ง๊ธ ์์ ๊ฒฝ์ฐ create ๋ก ์ธ์คํด์ค๋ฅผ ์์ฑํ์ง ์์๊ณ , ์๋ ์ ์ฒด ์ฝ๋์ ๋์์๋ค์ํผ state ์์ ์ธ์คํด์ค๋ฅผ ์ง์ ์์ฑํด์คฌ๋ค.
๋ฐ๋ผ์ dispose ์ญ์ ์๋์ ๊ฐ์ด ์ง์ ํด์ค์ผํ๋ค.
@override
void dispose() {
_counter.dispose();
super.dispose();
}์๋๋ ์ ์ฒด ์ฝ๋์ด๋ค.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart';
import 'show_me_counter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final Counter _counter = Counter();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Anonymous Route',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'/': (context) => ChangeNotifierProvider.value(
value: _counter,
child: MyHomePage(),
),
'/counter': (context) => ChangeNotifierProvider.value(
value: _counter,
child: ShowMeCounter(),
),
},
);
}
@override
void dispose() {
_counter.dispose();
super.dispose();
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: Text(
'Show Me Counter',
style: TextStyle(fontSize: 20.0),
),
onPressed: () {
Navigator.pushNamed(context, '/counter');
},
),
SizedBox(height: 20.0),
ElevatedButton(
child: Text(
'Increment Counter',
style: TextStyle(fontSize: 20.0),
),
onPressed: () {
context.read<Counter>().increment();
},
)
],
),
),
);
}
}
Provider Access - Generated route access, Global access
ํ์ตํ Provider Access ์ ๋ํด์ ๋ฌด์์ ํ๊ณ , ๋ฌด์์ด ๋จ์๋์ง ๋ค์ ์ง์ด๋ณธ๋ค.
Provider Access
Anonymous route access Navigator.push ๋ด์์ ์ฌ์ฉํ screen ์ value constructor ๋ก ๊ฐ์ธ์ฃผ๋, context ๋ฅผ Navigator.push ์ ๊ฒ์ ์ฌ์ฉํ๋ค.
Named route access routes ์ค์ ๋ถ๋ถ์์ value constructor ๋ก ๊ฐ์ธ์ฃผ๋, ์ฌ์ฉํ ์ธ์คํด์ค๊ฐ create ๋ก ๋ง๋ค์ด์ง๊ฒ ์๋๋ผ ์๋์ผ๋ก dispose ๋์ง ์์ผ๋ฏ๋ก ์ง์ ํด๋น ์ธ์คํด์ค๋ฅผ dispose ์ฒ๋ฆฌ ํด์ค๋ค.
Generated route access Named route access ๊ณผ ๊ฑฐ์ ๋์ผํ๋ค. ์๋ ์ฝ๋๋ฅผ ๋ณด์.
class _MyAppState extends State<MyApp> {
final Counter _counter = Counter();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Anonymous Route',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(
builder: (context) => ChangeNotifierProvider.value(
value: _counter,
child: MyHomePage(),
),
);
case '/counter':
return MaterialPageRoute(
builder: (context) => ChangeNotifierProvider.value(
value: _counter,
child: ShowMeCounter(),
),
);
default:
return null;
}
},
);
}
@override
void dispose() {
_counter.dispose();
super.dispose();
}
}Global access ์ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด ์ต ์๋จ์ Provider ๋ก ๊ฐ์ธ๋๋ฉด ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
return Provider<T>(
create: (_) => T(),
child: MaterialApp(
...
),
);ํ์ง๋ง ๋ถ์ ํ๊ฒฝ ๋ฐ ์ ์ง๋ณด์ ๋ฑ์ ๊ณ ๋ คํ ๋ ์ ์ ์ง ๋ชปํ ๋ฐฉ์์ด๋ค. ์ค๋ฌด์์ ์ด๋ ๊ฒ ์ฌ์ฉํ ์ธ์คํด์ค๊ฐ ์์๊น ์ถ๋ค. ์ ์ ๋ก๋ฉ์คํผ๋๋ฅผ ์ด๋ ๊ฒ global access ๊ฐ๋ฅํ๊ฒ ์ฒ๋ฆฌํด์ ์ฌ์ฉํ๋ ๊ฒ ๊ฐ๊ธฐ๋ ํ๊ณ .. ์คํ์์ค ํ๋ก์ ํธ๋ค์ ํ๊ตฌํ ๋ ํ์ธํด๋ณด๋๋ก ํ์.
ProxyProvider - ๊ฐ์ 1

Provider ์์ ๋ค๋ฅธ Provider ์ ๊ฐ์ด ํ์ํ ๊ฒฝ์ฐ์ ProxyProvider ๋ฅผ ์ฌ์ฉํ๋ค.
๋ค๋ฅธ Provider ์ ๊ธฐ๋ฐํ์ฌ value ๋ฅผ ๋ง๋๋ Provider (A provider that builds a value based on other provider)
๋ค๋ฅธ Provider ์ ๊ฐ์ ์์กดํด์ value ๋ฅผ ๋ง๋๋ Provider ์ธ ๊ฒ์ด๋ค.
๊ผญ ๋ค๋ฅธ Provider ์ ์์กดํ์ง ์๋๋ผ๋, ์ด๋ค ๋ณํ๋ ๊ฐ์ ์์กดํด์ผ ํ๋ค๋ฉด ProxyProvider ๋ฅผ ์ฌ์ฉํ๋ค.

ProxyProvider ๋ ์ข ๋ฅ๊ฐ ๋ง๋ค.


create ๋ optional ์ด๊ณ update ๊ฐ required ์ด๋ค.
ProxyProvider ๊ฐ ์์ฒด์ ์ผ๋ก ๋ง๋ค๊ณ ๊ด๋ฆฌํด์ผํ value ๊ฐ ์๋ค๋ฉด create ๊ฐ ํ์ํ์ง๋ง, ๊ทธ๋ฐ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด ๋ง๋ค ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ create ๋ ํ ๋ฒ๋ง called, ๋๊ณ update ๋ ์ฌ๋ฌ๋ฒ called ๋๋ค.
ProxyProvider ๋ ๋ค๋ฅธ Provider ๊ฐ ์ ๊ณตํ๋ ๊ฐ์ ์์กดํ๋๋ฐ, ๋ค๋ฅธ Provider ๊ฐ ์ ๊ณตํ๋ value ๊ฐ ๋ฐ๋๋ฉด ์ด๋ฅผ ๋ฐ๋ผ๋ณด๊ณ ์๋(์์กดํ๊ณ ์๋) ProxyProvider ๊ฐ ์ ๊ณตํ๋ ๊ฐ์ด ๋ฐ๋์ด์ผ ํ๋ฏ๋ก update ๊ฐ ์ฌ๋ฌ๋ฒ called ๋๋ ๊ฒ์ ๋งค์ฐ ๋น์ฐํ ์ผ์ด๋ค.


update ๊ฐ ํธ์ถ๋๋ ๊ฒฝ์ฐ๋ค์ ์๋์ ๊ฐ๋ค.
ProxyProvider ๊ฐ ๊ฐ์ฅ ์ฒ์์ผ๋ก ์์กดํ๊ณ ์๋ ๋ค๋ฅธ Provider ์ ๊ฐ์ ์ป์ ๊ฒฝ์ฐ
ProxyProvider ๊ฐ ์์กดํ๊ณ ์๋ ๋ค๋ฅธ Provider ๊ฐ ์ ๊ณตํ๋ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ
ProxyProvider ๊ฐ rebuild ๋๋ ๊ฒฝ์ฐ
ProxyProvider - ๊ฐ์ 2




The instance of MyChangeNotifier is updated whenever myModel changes.
The same instance of MyChangeNotifier is used again and again, not created again. (์๋ก ๋ง๋ค์ด์ง์ง ์๊ณ ํ๋ฒ ๋ง๋ค์ด์ง MyChangeNotifier ๊ฐ ๊ณ์ ์ฌ์ฉ๋๋ค.)

ProxyProvider - ์ค์ต & ์์
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
class WhyProxyProv extends StatefulWidget {
const WhyProxyProv({Key? key}) : super(key: key);
@override
_WhyProxyProvState createState() => _WhyProxyProvState();
}
class _WhyProxyProvState extends State<WhyProxyProv> {
int counter = 0;
void increment() {
setState(() {
counter++;
print('counter: $counter');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Why ProxyProvider'),
),
body: Center(
child: Provider<Translations>(
create: (_) => Translations(counter),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ShowTranslations(),
SizedBox(height: 20.0),
IncreaseButton(increment: increment),
],
),
),
),
);
}
}
class ShowTranslations extends StatelessWidget {
const ShowTranslations({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = Provider.of<Translations>(context).title;
return Text(
title,
style: TextStyle(fontSize: 28.0),
);
}
}
class IncreaseButton extends StatelessWidget {
final VoidCallback increment;
const IncreaseButton({
Key? key,
required this.increment,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: increment,
child: Text(
'INCREASE',
style: TextStyle(fontSize: 20.0),
),
);
}
}create ๋ ํ ๋ฒ๋ง ์คํ๋๋ฏ๋ก increment ์ ์ํด์ counter ๊ฐ์ด ์ฆ๊ฐํด๋ ๊ฒฐ๊ตญ
create: (_) => Translations(counter)์์ ๋ฑ ํ๋ฒ ๋ง๋ค์ด์ง ์ธ์คํด์ค๋ ๋ง๋ค์ด์ง๋ ๋น์์ counter ๋ก ๋ง๋ค์ด์ก๊ธฐ ๋๋ฌธ์final title = Provider.of<Translations>(context).title;์ ๊ฐ์ create ๋ ๋ง๋ค์ด์ง ์ธ์คํด์ค ๊ทธ๋๋ก๋ค.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
class ProxyProvUpdate extends StatefulWidget {
const ProxyProvUpdate({Key? key}) : super(key: key);
@override
_ProxyProvUpdateState createState() => _ProxyProvUpdateState();
}
class _ProxyProvUpdateState extends State<ProxyProvUpdate> {
int counter = 0;
void increment() {
setState(() {
counter++;
print('counter: $counter');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ProxyProvider update'),
),
body: Center(
child: ProxyProvider0<Translations>(
update: (_, __) => Translations(counter),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ShowTranslations(),
SizedBox(height: 20.0),
IncreaseButton(increment: increment),
],
),
),
),
);
}
}
class ShowTranslations extends StatelessWidget {
const ShowTranslations({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = Provider.of<Translations>(context).title;
return Text(
title,
style: TextStyle(fontSize: 28.0),
);
}
}
class IncreaseButton extends StatelessWidget {
final VoidCallback increment;
const IncreaseButton({
Key? key,
required this.increment,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: increment,
child: Text(
'INCREASE',
style: TextStyle(fontSize: 20.0),
),
);
}
}์ ๊ณตํด์ผํ ๊ฐ์ด ๊ฐ๋ณ์ ์ธ ๊ฐ์ธ counter ์ ์์กดํ๋ ์ธ์คํด์ค์ด๋ฏ๋ก ProxyProvider ๋ฅผ ์ฌ์ฉํด์ค ์ฝ๋์ด๋ค.
์ด๋ฏธ counter ๊ฐ์ด 0 ์ผ๋ก ์ด๊ธฐํ ๋์ด ์์ด์, create ๊ฐ ํ์๊ฐ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์ ๊ณตํด์ผํ ๊ฐ๋ณ ์ธ์คํด์ค๊ฐ ํ๋๋ค. ๊ทธ๋์ ProxyProvider0 ๋ฅผ ์ฌ์ฉํ๋ค.(๊ณต์๋ฌธ์ ์ฐธ๊ณ )
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Translations {
late int _value;
void update(int newValue) {
_value = newValue;
}
String get title => 'You clicked $_value times';
}
class ProxyProvCreateUpdate extends StatefulWidget {
const ProxyProvCreateUpdate({Key? key}) : super(key: key);
@override
_ProxyProvCreateUpdateState createState() => _ProxyProvCreateUpdateState();
}
class _ProxyProvCreateUpdateState extends State<ProxyProvCreateUpdate> {
int counter = 0;
void increment() {
setState(() {
counter++;
print('counter: $counter');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ProxyProvider create/update'),
),
body: Center(
child: ProxyProvider0<Translations>(
create: (_) => Translations(),
update: (_, Translations? translations) {
translations!.update(counter);
return translations;
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ShowTranslations(),
SizedBox(height: 20.0),
IncreaseButton(increment: increment),
],
),
),
),
);
}
}
class ShowTranslations extends StatelessWidget {
const ShowTranslations({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = context.watch<Translations>().title;
return Text(
title,
style: TextStyle(fontSize: 28.0),
);
}
}
class IncreaseButton extends StatelessWidget {
final VoidCallback increment;
const IncreaseButton({
Key? key,
required this.increment,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: increment,
child: Text(
'INCREASE',
style: TextStyle(fontSize: 20.0),
),
);
}
}create ์์ ์์ฑ์ ๋จผ์ ํ ์ผ์ด์ค๋ค.
update ์์ create ์์ ์์ฑ๋ ๊ฐ์ฒด๋ฅผ ๋ฐ์ .update() ๋ฅผ call ํ๊ณ return ํ๋ค.
์ฌ๊ธฐ์ ๋ update ๋ฐ์ํ๋ฉด ์ฒ์ create ๋ฐ์์ ๋ง๋ค์ด์ง ์ธ์คํด์ค(= ์ฒ์ update ๋ ์ฌ์ฉ๋)๋ฅผ ๊ณ์ ์ฐธ์กฐํ์ฌ ์ฌํ์ฉํ๋ค. (update ํ๋ค๊ณ ์๋ก ๋ง๋ค์ด์ return ํ๋ ๊ฒ์ด ์๋๋ผ๋ ๊ฒ์ ๋ช ์ฌ)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
class ProxyProvProxyProv extends StatefulWidget {
const ProxyProvProxyProv({Key? key}) : super(key: key);
@override
_ProxyProvProxyProvState createState() => _ProxyProvProxyProvState();
}
class _ProxyProvProxyProvState extends State<ProxyProvProxyProv> {
int counter = 0;
void increment() {
setState(() {
counter++;
print('counter: $counter');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ProxyProvider ProxyProvider'),
),
body: Center(
child: MultiProvider(
providers: [
ProxyProvider0<int>(
update: (_, __) => counter,
),
ProxyProvider<int, Translations>(
update: (_, value, __) => Translations(value),
),
],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ShowTranslations(),
SizedBox(height: 20.0),
IncreaseButton(increment: increment),
],
),
),
),
);
}
}
class ShowTranslations extends StatelessWidget {
const ShowTranslations({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = context.watch<Translations>().title;
return Text(
title,
style: TextStyle(fontSize: 28.0),
);
}
}
class IncreaseButton extends StatelessWidget {
final VoidCallback increment;
const IncreaseButton({
Key? key,
required this.increment,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: increment,
child: Text(
'INCREASE',
style: TextStyle(fontSize: 20.0),
),
);
}
}๋ ๊ฐ์ ProxyProvider ๋ฅผ ์ฌ์ฉํ๋ ์ผ์ด์ค.
ProxyProvider0 ๋ ๊ฐ๋ณ์ ์ธ int value ๋ฅผ return
ProxyProvider ๋ ProxyProvider0 ๊ฐ ์ ๊ณตํ๋ ๊ฐ๋ณ์ ์ธ int ๋ฅผ ๋ฐ์์ ์๋ก์ด ์ธ์คํด์ค๋ฅผ return
์ด ๊ฒฝ์ฐ์ ProxyProvider ๊ฐ ํญ์ ์๋ก์ด ์ธ์คํด์ค๋ฅผ ๋ง๋ค์ด์ return ํ๊ณ ์๋ค๋ ๊ฒ์ ๋ช ์ฌ. ๋งค๋ฒ ์๋ก์ด Translations ๋ฅผ ๋ง๋ค์ด์ return ํ๊ณ ์๋ค.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Counter with ChangeNotifier {
int counter = 0;
void increment() {
counter++;
notifyListeners();
}
}
class Translations with ChangeNotifier {
late int _value;
void update(Counter counter) {
_value = counter.counter;
notifyListeners();
}
String get title => 'You clicked $_value times';
}
class ChgNotiProvChgNotiProxyProv extends StatefulWidget {
const ChgNotiProvChgNotiProxyProv({Key? key}) : super(key: key);
@override
_ChgNotiProvChgNotiProxyProvState createState() =>
_ChgNotiProvChgNotiProxyProvState();
}
class _ChgNotiProvChgNotiProxyProvState
extends State<ChgNotiProvChgNotiProxyProv> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ChangeNotifierProvider ChagneNotifierProxyProvider'),
),
body: Center(
child: MultiProvider(
providers: [
ChangeNotifierProvider<Counter>(
create: (_) => Counter(),
),
ChangeNotifierProxyProvider<Counter, Translations>(
create: (_) => Translations(),
update: (
BuildContext _,
Counter counter,
Translations? translations,
) {
translations!..update(counter);
return translations;
},
),
],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ShowTranslations(),
SizedBox(height: 20.0),
IncreaseButton(),
],
),
),
),
);
}
}
class ShowTranslations extends StatelessWidget {
const ShowTranslations({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = context.watch<Translations>().title;
return Text(
title,
style: TextStyle(fontSize: 28.0),
);
}
}
class IncreaseButton extends StatelessWidget {
const IncreaseButton({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => context.read<Counter>().increment(),
child: Text(
'INCREASE',
style: TextStyle(fontSize: 20.0),
),
);
}
}์ ๋ง ์ฌ๋ฌ๊ฐ์ง ๋ฐฉ์์ผ๋ก ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๊ตฌํํ ์ ์๋ค.
Counter ๊ฐ Value Object ๋ก ์ฌ์ฉ ๋์๋ค.
IncreaseButton ์์๋ ๋จ์ง Counter ์ access ๋ง ํ๋ค. increment() ๋ฅผ ์คํํ๊ธฐ๋ง ํ ๋ชฉ์ ์ด๊ธฐ ๋๋ฌธ.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Counter with ChangeNotifier {
int counter = 0;
void increment() {
counter++;
notifyListeners();
}
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
class ChgNotiProvProxyProv extends StatefulWidget {
const ChgNotiProvProxyProv({Key? key}) : super(key: key);
@override
_ChgNotiProvProxyProvState createState() => _ChgNotiProvProxyProvState();
}
class _ChgNotiProvProxyProvState extends State<ChgNotiProvProxyProv> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ChangeNotifierProvider ProxyProvider'),
),
body: Center(
child: MultiProvider(
providers: [
ChangeNotifierProvider<Counter>(
create: (_) => Counter(),
),
ProxyProvider<Counter, Translations>(
update: (_, counter, __) => Translations(counter.counter),
),
],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ShowTranslations(),
SizedBox(height: 20.0),
IncreaseButton(),
],
),
),
),
);
}
}
class ShowTranslations extends StatelessWidget {
const ShowTranslations({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = context.watch<Translations>().title;
return Text(
title,
style: TextStyle(fontSize: 28.0),
);
}
}
class IncreaseButton extends StatelessWidget {
const IncreaseButton({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => context.read<Counter>().increment(),
child: Text(
'INCREASE',
style: TextStyle(fontSize: 20.0),
),
);
}
}์ง์ ์ ์ดํด๋ณธ ChangeNotifierProvider, ChangeNotifierProxyProvider ์กฐํฉ์ ๊ธฐ์กด์ Translations ๋ฅผ mutation ์์ผ์ ์ฌํ์ฉ ํ๋ ๋ฐ๋ฉด ์ด ๋ฐฉ์์ Counter ๊ฐ ๋ณํ ๋๋ง๋ค ๋งค๋ฒ ์๋ก์ด Translations ์ธ์คํด์ค๋ฅผ return ํ๋ค.
๊ฐ์ฌ๋ ์ด์ผ๊ธฐ๋ก๋ ๋จ์ํ computed state ๋ฅผ ๋ง๋ค์ด ๋ด๋ ๊ฒฝ์ฐ ChangeNotifierProvider ๋ฅผ ์ฐ์ง ์๊ณ ProxyProvider ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋งค๋ด์ผ์๋ ๋์์๋ preferred way ๋ผ๊ณ ํ๋ค.
๋จ์ํ computed state ๋ฅผ ๋ง๋ค์ด ๋ด๋ ๊ฒฝ์ฐ ChangeNotifierProvider ๋ฅผ ์ฐ์ง ์๊ณ ProxyProvider ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋งค๋ด์ผ์๋ ๋์์๋ preferred way ๋ผ๊ณ ํ๋ค.
Errors & addPostFrameCallback - 1



Errors & addPostFrameCallback - 2

์์ ์๋ฌ๋ ์๋์ ๊ฐ์ ์ฝ๋์์ ๋ฐ์๋์๋ค.
flutter ๊ฐ Widget ๋ค์ ๊ทธ๋ฆฌ๊ณ ์๋ ์ค์ ๋ค์ build ์์ฒญ์ ํ ์ ์๋ค๋ ๊ฒ.
class Counter with ChangeNotifier {
int counter = 0;
void increment() {
counter++;
notifyListeners();
}
}@override
void initState(){
super.initState();
context.read<Counter>().increament();
myCounter = context.read<Counter>().counter + 10;
}


WidgetsBinding.instance!.addPostFrameCallback((_) {
context.read<Counter>().increment();
myCounter = context.read<Counter>().counter + 10;
});addPostFrameCallback ์ ํ์ฌ์ Frame ์ด ์์ฑ๋ ํ ๋ฑ๋ก๋ callback ์ ์คํ์ํค๋๋ก ํ๋ค.
add(์ถ๊ฐ) + PostFrameCallback(Frame ๋ค ๊ทธ๋ ค์ง ํ ๋ถ๋ ค์ง Callback)
UI์ ์ํฅ์ ๋ฏธ์น๋ action ์ ์คํ์ ํ์ฌ์ Frame ์ดํ๋ก ์ง์ฐ์ํฌ ์ ์๋ค. โํ์ฌ์ Frame ์ดํโ ๋ผ๋ ๋ป์ ๊ฒฐ๊ตญ ํ์ฌ ์ธ์ดํด์ build ๊ฐ ๋๋ ํ๋ฅผ ์๋ฏธํ๋ค.
๋ค์ ๋งํ๋ฉด ํด๋น Widget ์ build ๊ฐ ๋๋ ํ ์คํ๋ action ์ ์์ฝํ ์ ์๋ ๊ฒ์ด๋ค.
addPostFrameCallback ์ธ์ ์๋์ ๊ฐ์ ์ฒ๋ฆฌ๋ ๋์ผํ ์๋ฆฌ์ด๋ค.
Future.delayed(Duration(seconds: 0), () {
context.read<Counter>().increment();
myCounter = context.read<Counter>().counter + 10;
});Future.microtask(() {
context.read<Counter>().increment();dartdddd
myCounter = context.read<Counter>().counter + 10;
});
์ error ๋
context.read์์ read ๋ฅผ watch ๋ก ๋ฐ๊พธ๋ฉด ๋ณผ ์ ์๋ ์๋ฌ์ด๋ค.Widget Tree ๋ฐ์์ listen ์ ํ๋ ค ํ๋ค๋ ๊ฒ.
์๊ฐ์ฐจ๋ฅผ ๋๊ณ ํ์ฌ Frame ๋๋๊ณ ์ด๊ฑธ ์คํํด๋ฌ๋ผ๋ ์์ฝ ํ์์์ listen ์ ํ๋ค๋ ๊ฒ์ด ๋ ผ๋ฆฌ์ ์ผ๋ก ๋ง์ง ์๋ค. ์ด๋ ๊ฒ ์ดํดํ์.
Errors & addPostFrameCallback - 3
Errors & addPostFrameCallback - 3 ์์๋ ํน์ ํ๋ฉด์ ์ง์
ํ์๋ ํน์ ๊ฐ๋ณ์ ์ธ ๊ฐ์ด ํน์ ์กฐ๊ฑด์ ๊ฑธ๋ ธ์ ๋ Dialog ๋ฅผ ๋์ฐ๋ ๊ฒฝ์ฐ์ ์์ด์ ์ฒ๋ฆฌ ๋ฐฉ์ ๋ค์ ์ดํด๋ณธ๋ค.

Dialog ๋ ํด๋น Screen ์ด ๋ค ๊ทธ๋ ค์ง๊ณ ๋์ ๊ทธ ์์ ๊ทธ๋ ค์ง๋ overlay Widget.
๋ฐ๋ผ์ initState ์์ ๊ทธ๋ฅ ํธ์ถ์ ์์ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ด ๋น์ฐํ๋ค.
์ด ๊ฒฝ์ฐ๋ ์๋์ ๊ฐ์ด ํด๋น Frame ์ด ๋ค ๊ทธ๋ ค์ง๊ณ ๋์ ๊ทธ ์ดํ์ ๊ทธ๋ ค์ง๋๋ก ์ฒ๋ฆฌํด์ผ ํ๋ค.
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Be careful!'),
);
},
);
});
}
๊ฐ์ ๋งฅ๋ฝ์์ ๊ฐ๋ณ์ ์ธ ๊ฐ์ ๋ฐ๋ผ๋ณด๊ณ ํน์ ์กฐ๊ฑด์์ Dialog ๋ฅผ ๋์ฐ๊ณ ์ถ์๋๋ ์๋์ ๊ฐ์ด addPostFrameCallback ๋ก ์ฒ๋ฆฌํด์ค์ผํ๋ค.
ํด๋น Frame ์ด ๋ค ๊ทธ๋ ค์ง๊ณ ๋์ ์ดํ์ ์กฐ๊ฑด ๊ฒ์ฌ๋ฅผ ํ ํ overlay ํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
์กฐ๊ฑด์์ ํฌํจํ Dialog call ์์ฒด๋ฅผ ๋ชจ๋ addPostFrameCallback ๋ก ์์ฝํ ์ ์์ผ๋ ์กฐ๊ฑด์ ๋ถํฉํ์ง๋ ์๋ ๊ฒฝ์ฐ์๋ ๋ถํ์ํ๊ฒ action ์ด register ๋๋ฏ๋ก ์ข์ง ์์ ์ฝ๋๊ฐ ๋๋ค.
@override
Widget build(BuildContext context) {
if (context.read<Counter>().counter == 3) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Count is 3'),
);
},
);
});
}
return Scaffold(
appBar: AppBar(
title: Text('Handle Dialog'),
),
body: Center(
child: Text(
'${context.watch<Counter>().counter}',
style: TextStyle(fontSize: 40.0),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
context.read<Counter>().increment();
},
),
);
}




Errors & addPostFrameCallback - 4
Errors & addPostFrameCallback - 4 ์์๋ State ๊ฐ ๋ณํ ๋ Navigate ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ดํด๋ณธ๋ค.

Navigate ํ๋ ๊ฒ๋ ๊ฒฐ๊ตญ Stack ์์ ์ฌ๋ฆฌ๋ Overlay ํ์์ด๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด ์์ฝ์ ๊ฑธ์ด ์ฃผ์ด์ผ ํ๋ค.
๋ง์ฝ addPostFrameCallback ๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด build ํ๋ ์์ค์ Navigator.push ๊ฐ ์ฒ๋ฆฌ ๋๋ ๊ฒ์ด๊ณ ์ด๋ ๋ณธ ํ๋ฉด์ด ๋ค ๊ทธ๋ ค์ง๊ธฐ๋ ์ ์(์ ํํ๋ ๊ทธ๋ ค์ง๋ ์ค๊ฐ์) Overlay ์์ฒญ์ ํ ๊ฒ๊ณผ ๊ฐ๋ค. ๊ทธ๋์ ์์ ๊ฐ์ ์๋ฌ๋ฅผ ๋ง๋๊ฒ ๋๋ค.
if (context.read<Counter>().counter == 3) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OtherPage(),
),
);
});
}
Dialog, Navigate, BottomSheet ๋ชจ๋ Overlay Widget ์ด๋ฏ๋ก ํด๋น Widget ์ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ด๋ฒ์ ํ์ตํ ๋ด์ฉ๋ค์ ์ ํ์ฉํ๋๋ก ํ๋ค.
ChangeNotifier ์ addListener ๋ฅผ ์ด์ฉํ action ์ฒ๋ฆฌ





๊ฐ์ฌ๋์ ๋๋ฒ์งธ ํน์ ์ธ๋ฒ์งธ ๋ฐฉ์์ ์ถ์ฒ.
๋๋ฒ์งธ ๋ฐฉ์๋ง ์๋์ ์ฝ๋๋ก ์ ๋ฆฌ.
Future<void> getResult(String searchTerm) async {
_state = AppState.loading;
notifyListeners();
await Future.delayed(Duration(seconds: 1));
try {
if (searchTerm == 'fail') {
throw 'Something went wrong';
}
_state = AppState.success;
notifyListeners();
} catch (e) {
_state = AppState.error;
notifyListeners();
rethrow;
}
} void submit() async {
setState(() {
autovalidateMode = AutovalidateMode.always;
});
final form = formKey.currentState;
if (form == null || !form.validate()) return;
form.save();
try {
await context.read<AppProvider>().getResult(searchTerm!);
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return SuccessPage();
},
));
} catch (e){
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Something went wrong'),
);
},
);
}
}
Last updated