Bloc Pattern을 단순한 구조로 만들어주는 Provider Pattern.
1. CountProvider.dart
import "package:flutter/material.dart";
class CountProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void plus() {
_count++;
notifyListeners();
}
void minus() {
_count--;
notifyListeners();
}
}
import "package:flutter/foundation.dart";
class CountProvider with ChangeNotifier, DiagnosticableTreeMixin {
int _count = 0;
int get count => _count;
void plus() {
_count++;
notifyListeners();
}
void minus() {
_count--;
notifyListeners();
}
/// Makes `Counter` readable inside the devtools by listing all of its properties
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('count', count));
}
}
2. main.dart
import "package:flutter/material.dart";
import "package:provider/provider.dart";
import "package:provider_example/CountProvider.dart";
import "package:provider_example/home.dart";
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Provider Sample",
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity
),
// home: ChangeNotifierProvider(
// create: (BuildContext context) => CountProvider(),
// child: const Home(title: "Provider example"),
// ),
home: MultiProvider(
providers: [
ChangeNotifierProvider(create: (BuildContext context) => CountProvider()),
],
child: const Home(title: "Provider example"),
),
);
}
}
3. home.dart
Provider.of<T>(context)를 이용한 Provider객체 Method호출 방법
import "package:flutter/material.dart";
import "package:provider/provider.dart";
import "package:provider_example/CountProvider.dart";
import "package:provider_example/CountView.dart";
class Home extends StatelessWidget {
final String title;
const Home({super.key, required this.title});
@override
Widget build(BuildContext context) {
print("Widget : Home");
// listen: false는 현재 Widget을 Rendering하지 않도록 한다.
// But 하위 Widget은 여전히 다시 Rendering된다. 하위 Widget의 Rendering을 방지할려면 하위 Widget에서 Consumer를 사용해야 한다.
CountProvider countProvider = Provider.of<CountProvider>(context, listen: false);
return Scaffold(
appBar: AppBar(title: Text(title)),
body: const CountView(),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
IconButton(
// 아래 두가지 모두 정상 작동된다.
onPressed: () => countProvider.plus(),
//onPressed: () => context.read<CountProvider>().plus(),
icon: const Icon(Icons.add)),
IconButton(
// 아래 두가지 모두 정상 작동된다.
onPressed: () => countProvider.minus(),
//onPressed: () => context.read<CountProvider>().minus(),
icon: const Icon(Icons.remove))
],
),
);
}
}
context.read<Provider T>는 현재 시점의 상태 값을 한번만 읽어오고, Provider객체에 접근은 하지만 구독하지는 않는다.
Provider객체의 상태가 변경되어도 read를 호출한 위젯은 rebuild되지 않는다.
Provider.of<CountProvider>(context, listen: true); 는 context.watch<CountProvider>(); 와 같다
Provider.of<CountProvider>(context, listen: false); 는 context.read<CountProvider>(); 와 같다.
기본값 : listen: true, 값이 변할 때 UI도 같이 변해야 하면 true, 단순히 기능(함수)만 쓸 거라면 false를 선택
4. CountView.dart
Provider객체를 이용한 상태값 참조
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:study/provider/CountProvider.dart';
class CountView extends StatelessWidget {
const CountView({super.key});
@override
Widget build(BuildContext context) {
print("Widget : CountView");
// 1. Provider.of<T>(content)
// 현재 Widget을 반복하여 Rendering한다.
// return Center(
// child: Text(
// Provider.of<CountProvider>(context).count.toString(),
// style: const TextStyle(fontSize: 80),
// ),
// );
// 2. context.watch<T>()
// 현재 Widget을 반복하여 Rendering한다.
// return Center(
// child: Text(
// context.watch<CountProvider>().count.toString(),
// style: const TextStyle(fontSize: 80),
// ),
// );
// 3. Consumer<T>(builder: (BuildContext context, T provider, Widget? child) {}
// 현재 Widget을 반복하여 Rendering하지 않는다.
return Center(
child: Consumer<CountProvider>(
builder: (context, provider, child) {
return Text(
provider.count.toString(),
style: const TextStyle(fontSize: 80),
);
},
),
);
// 4. context.select<T, R>()
// 현재 Widget을 반복하여 Rendering한다.
// return Center(
// child: Text(
// context.select<CountProvider, int>((provider) => provider.count).toString(),
// style: const TextStyle(fontSize: 80),
// ),
// );
// 5. Selector<T, R>
// 현재 Widget을 반복하여 Rendering하지 않는다.
return Center(
child: Selector<CountProvider, int>(
selector: (context, provider) => provider.count,
builder: (context, value, child) {
return Text(
'$value',
style: const TextStyle(fontSize: 80),
);
},
),
);
}
}
2. context.watch<CountProvider>().count.toString()
위 두가지 방법 모두 widget rebuild 발생한다.
3. Consumer객체를 이용해야 Provider객체를 구독하는 Widget이 rebuild되는 것을 방지할 수 있다.
4. context.select<CountProvider, int>() Widget rebuild 발생
5. Selector객체를 이용하면 Consumer객체를 이용한것과 같이 구독하는 Widget이 rebuild되는 것을 방지할 수 있다.
Selector와 Consumer의 차이는
Consumer는 Provider객체의 모든 맴버의 변화를 모두 감지하여 Consumer내부 전체가 Rebuild되지만,
Selector는 Provider객체의 특정 맴버의 변화만을 감지하게 할 수 있어 성능이 더 좋다.
context.read<CountProvider>().count.toString() 로 하면 작동안된다.
context.read와 context.watch 기능 차이