Provider - ChangeNotifier๋ก ์ํ ๊ณต์ ํ๊ธฐ
์ง๋ ๊ธ์์ InheritedWidget์ผ๋ก ๊ฐ์ ํธ๋ฆฌ ์๋๋ก ๋ด๋ ค๋ณด๋ด๋ ๋ฒ์ ๋ดค๋ค. ์ฝ๋ ์ชฝ์ ํธํด์ก์ง๋ง ๊ฐ์ ๋ฐ๊พธ๋ ๊ฑด ์ฌ์ ํ ์์ชฝ State์ setState์ ๊ธฐ๋์ผ ํ๋ค. ์ด๋ฒ์ ๊ฐ๊ณผ ๊ทธ ๊ฐ์ ๋ฐ๊พธ๋ ํจ์๋ฅผ ํ ํด๋์ค์ ๋ฌถ์ด์ ํ์ํ ์์ ฏ ์ด๋์๋ ์ฐ๊ฒ ํด๋ณด์. ๋จผ์ ๊ทธ๋ฅ ํ๋ฒํ ํด๋์ค๋ก ์๋ํด๋ณธ๋ค.
class CounterModel {
int count = 0;
void increment() {
count++;
}
}์ด ๊ฐ์ฒด๋ฅผ ํธ๋ฆฌ์ ๊ฑธ์ด๋๊ณ ํ๋ฉด์์ ์ฐ๋ ์ฝ๋๋ ์ด๋ ๋ค. Provider ํจํค์ง๋ฅผ ์ฐ๋ฏ๋ก ๋จผ์ ์ค์นํ๋ค.
flutter pub add providerimport 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
Provider(
create: (_) => CounterModel(), // ๊ฐ์ฒด๋ฅผ ํธ๋ฆฌ์ ๊ฑธ์ด๋
child: const MyApp(),
),
);
}
class CountText extends StatelessWidget {
const CountText({super.key});
@override
Widget build(BuildContext context) {
final count = context.watch<CounterModel>().count;
return Text('$count');
}
}
class PlusButton extends StatelessWidget {
const PlusButton({super.key});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => context.read<CounterModel>().increment(),
child: const Text('+1'),
);
}
}๋ฒํผ์ ๋๋ฌ๋ ์ซ์๋ ๊ณ์ 0์ด๋ค. increment() ์์ print(count)๋ฅผ ๋ฃ์ด๋ณด๋ฉด ์ฝ์์๋ 1, 2, 3์ด ์ฌ๋ผ๊ฐ๋ค. ๊ฐ์ ๋ถ๋ช
ํ ๋ฐ๋๋๋ฐ ํ๋ฉด์ด ๊ทธ๋๋ก์ธ, setState ๊ธ์์ ๊ฒช์ ์ฆ์๊ณผ ๋๊ฐ๋ค.
์์ธ: ๊ฐ์ด ๋ฐ๋์๋ค๊ณ ์๋ฌด๋ ์๋ ค์ฃผ์ง ์๋๋ค
CounterModel์ ํ๋ฒํ ํด๋์ค๋ผ์ count++๊ฐ ์ผ์ด๋๋ "๋ฐ๋์๋ค"๋ ์ ํธ๊ฐ ์ด๋๋ก๋ ๊ฐ์ง ์๋๋ค. CountText๋ ์ฒ์ ํ ๋ฒ ๊ฐ์ ์ฝ๊ณ ๋์ด๋ค. ํ๋ฉด์ ๋ค์ ๊ทธ๋ฆฌ๋ ค๋ฉด ๊ฐ์ ๊ฐ์ง ์ชฝ์ด ๊ตฌ๋
์์๊ฒ ์๋ ค์ฃผ๋ ์ฅ์น๊ฐ ์์ด์ผ ํ๋ค.
ํด๊ฒฐ: ChangeNotifier๋ก ๋ฐ๋์๋ค๊ณ ์๋ฆฌ๊ธฐ
import 'package:flutter/foundation.dart';
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // ๊ตฌ๋
์์๊ฒ ๋ฐ๋์๋ค๊ณ ์๋ฆผ
}
}๊ทธ๋ฆฌ๊ณ ๊ฑธ์ด๋๋ ์ชฝ์ ChangeNotifierProvider๋ก ๋ฐ๊พผ๋ค.
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const MyApp(),
),
);
}์ด์ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ซ์๊ฐ ์ฌ๋ผ๊ฐ๋ค. ๋ฐ๋ ๊ฑด ๋ ๊ฐ์ง๋ค.
extends ChangeNotifier๋ก "๊ตฌ๋ ์ ๋ฐ์ ์ ์๋ ๊ฐ์ฒด"๊ฐ ๋๋ค- ๊ฐ์ ๋ฐ๊พผ ๋ค
notifyListeners()๋ฅผ ๋ถ๋ฌ์, ์ด ๊ฐ์watch๋ก ์ฝ์ ์์ ฏ๋ค์ ๋ค์ ๊ทธ๋ฆฌ๊ฒ ํ๋ค
notifyListeners()๋ฅผ ๋นผ๋จน์ผ๋ฉด ์ฒ์ ์ฆ์์ผ๋ก ๋์๊ฐ๋ค. setState๋ฅผ ๋นผ๋จน๋ ์ค์์ ๊ฐ์ ์๋ฆฌ๋ค.
๊ทธ๋ฆฌ๊ณ ํ๋๋ฅผ _count๋ก ์จ๊ธฐ๊ณ get count๋ง ์ด์ด๋ ์ด์ ๊ฐ ์๋ค. count๋ฅผ ๊ทธ๋ฅ ๊ณต๊ฐํด๋๋ฉด ๋ฐ์์ model.count = 5์ฒ๋ผ ๋ฐ๊ฟ๋ฒ๋ฆด ์ ์๋๋ฐ, ์ด๋ ๊ฒ ๋ฐ๊พธ๋ฉด notifyListeners()๊ฐ ๋ถ๋ฆฌ์ง ์์์ ํ๋ฉด์ด ์ ๋ฐ๋๋ค. ๊ฐ์ ๋ชจ๋ธ ์์ ๋ฉ์๋๋ก๋ง ๋ฐ๊พธ๊ฒ ๋ง์๋๋ ๊ฒ ์์ ํ๋ค.
CountText์ PlusButton์ด ๋ ๋ค StatelessWidget์ธ ์ ๋ ๋์ฌ๊ฒจ๋ณด์. ๊ฐ์ setState๋ก ๋ค๊ณ ์๋ ์์ ฏ์ด ์๊ณ , ํธ๋ฆฌ ์ด๋์ ๋๋ ๊ฐ์ CounterModel์ ์ด๋ค. ์ง๋ ๊ธ์ InheritedWidget์ ์ง์ ๋ง๋ค์ง ์์๋ ๋๋ค.
watch์ read์ ์ฐจ์ด
final count = context.watch<CounterModel>().count; // build ์์์
onPressed: () => context.read<CounterModel>().increment(), // ๋ฒํผ ์ฝ๋ฐฑ ์์์context.watch<T>(): ๊ฐ์ ์ฝ์ผ๋ฉด์ ๊ตฌ๋ ํ๋ค. ๊ฐ์ด ๋ฐ๋๋ฉด ์ด ์์ ฏ์ด ๋ค์ build๋๋ค.build์์์ ์ด๋ค.context.read<T>(): ๊ฐ์ ํ ๋ฒ๋ง ์ฝ๊ณ ๊ตฌ๋ ํ์ง ์๋๋ค.onPressed๊ฐ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์์์ ์ด๋ค.
์ง๋ ๊ธ์ dependOnInheritedWidgetOfExactType(๊ตฌ๋
)๊ณผ getInheritedWidgetOfExactType(ํ ๋ฒ๋ง)์ ๊ทธ๋๋ก ๋์ํ๋ค. ๋์ ๋ฐ๊ฟ ์ฐ๋ฉด ๋ฌธ์ ๊ฐ ์๊ธด๋ค.
๋ฒํผ ์ฝ๋ฐฑ ์์์ context.watch๋ฅผ ์ฐ๋ฉด ์๋ฌ๊ฐ ๋๋ค. ์ฝ๋ฐฑ์ build ์ค์ด ์๋๋ผ์ ๊ตฌ๋
ํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๊ฑฐ๊พธ๋ก build ์์์ context.read๋ก ๊ฐ์ ์ฝ์ผ๋ฉด ์๋ฌ๋ ์ ๋์ง๋ง, ๊ฐ์ด ๋ฐ๋์ด๋ ํ๋ฉด์ด ๊ฐฑ์ ๋์ง ์๋๋ค. ํ๋ฉด์ ๋ณด์ฌ์ค ๊ฐ์ watch, ๋์์ ์ํฌ ๋ read๋ก ๊ธฐ์ตํ๋ฉด ๋๋ค.
์ฝ์ง ํฌ์ธํธ: Provider๋ฅผ ๊ฑธ์ด๋ build์์ ๋ฐ๋ก ์ฝ์ผ๋ฉด ๋ชป ์ฐพ๋๋ค
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => CounterModel(),
child: Text('${context.watch<CounterModel>().count}'), // ์ฌ๊ธฐ์ ์๋ฌ๋จ
);
}
}ProviderNotFoundException์ด ๋๋ค. ์ด build์ context๋ ChangeNotifierProvider๋ณด๋ค ์์ ์์ด์, ์๋ก ์ฌ๋ผ๊ฐ๋ CounterModel์ ๋ชป ์ฐพ๋๋ค. ์ง๋ ๊ธ์ CountScope.of(context) ์๋ฌ์ ๊ฐ์ ์ด์ ๋ค. Provider๊ฐ InheritedWidget์ ๊ฐ์ผ ๊ฒ์ด๋ ๋น์ฐํ๋ค.
์ฝ๋ ์ชฝ์ ๋ณ๋ ์์ ฏ์ผ๋ก ๋นผ๊ฑฐ๋, Consumer๋ก ์ context๋ฅผ ์ป๋๋ค.
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: Consumer<CounterModel>(
builder: (context, model, child) => Text('${model.count}'),
),
)Consumer์ builder๊ฐ ๋ฐ๋ context๋ Provider ์๋์ ์์น๋ผ์ ์๋ก ์ฌ๋ผ๊ฐ๋ฉด ์ฐพ๋๋ค. ์์ ฏ์ ์๋ก ๋ง๋ค๊ธฐ ๋ฒ๊ฑฐ๋ก์ธ ๋ ํธํ๋ค.
์ฌ๋ฌ ๋ชจ๋ธ์ ๊ฑธ์ด๋๊ธฐ: MultiProvider
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CounterModel()),
ChangeNotifierProvider(create: (_) => ThemeModel()),
],
child: const MyApp(),
),
);
}Provider๋ฅผ ์ค์ฒฉํ๋ฉด ๋ค์ฌ์ฐ๊ธฐ๊ฐ ๊น์ด์ง๋ฏ๋ก MultiProvider๋ก ํ ์ค์ ๋์ดํ๋ค. ๋ชจ๋ธ์ ๋ณดํต ์ฑ ๋งจ ์(runApp ๊ทผ์ฒ)์ ๊ฑธ์ด์ ์ด๋ ํ๋ฉด์์๋ ์ธ ์ ์๊ฒ ํ๋ค.
ํ๋ฉด ์ผ๋ถ๋ง ๋ค์ ๊ทธ๋ฆฌ๊ธฐ: select
context.watch<CounterModel>()์ ๋ชจ๋ธ์ ๋ฌด์์ด๋ ๋ฐ๋๋ฉด ๋ค์ ๊ทธ๋ฆฐ๋ค. ๋ชจ๋ธ์ ํ๋๊ฐ ์ฌ๋ฌ ๊ฐ์ผ ๋ ํ๋๋ง ํ์ํ๋ฉด select๋ก ์ขํ๋ค.
final count = context.select<CounterModel, int>((m) => m.count);count๊ฐ ๋ฐ๋ ๋๋ง ์ด ์์ ฏ์ด ๋ค์ build๋๋ค. ์ฒ์์๋ watch๋ก ์์ํ๊ณ , ๋ถํ์ํ๊ฒ ์์ฃผ ๊ทธ๋ ค์ง๋ค๊ณ ๋๋ ๋ select๋ฅผ ์ฐ๋ฉด ๋๋ค.
Compose, SwiftUI์ ๋น๊ตํ๋ฉด
// Compose - ViewModel + StateFlow
class CounterViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count = _count.asStateFlow()
fun increment() { _count.value++ }
}
@Composable
fun CountText(vm: CounterViewModel = viewModel()) {
val count by vm.count.collectAsState()
Text("$count")
}// SwiftUI - ObservableObject
class CounterModel: ObservableObject {
@Published var count = 0
func increment() { count += 1 }
}
struct CountText: View {
@EnvironmentObject var model: CounterModel
var body: some View { Text("\(model.count)") }
}- ๊ฐ์ ๊ฐ์ง ๊ฐ์ฒด: Flutter
ChangeNotifier/ ComposeViewModel/ SwiftUIObservableObject - ๋ฐ๋ ๊ฑธ ์๋ฆฌ๋ ๋ฐฉ๋ฒ: Flutter๋
notifyListeners()๋ฅผ ์ง์ ํธ์ถ / ComposeStateFlow์ ๊ฐ ๋ณ๊ฒฝ / SwiftUI@Published๊ฐ ์๋์ผ๋ก - ํธ๋ฆฌ์ ๊ฑธ์ด๋๊ธฐ: Flutter
ChangeNotifierProvider/ ComposeviewModel()๋ก ๊ฐ์ ธ์ด / SwiftUI.environmentObject - ์ฝ๊ณ ๊ตฌ๋
ํ๊ธฐ: Flutter
context.watch/ ComposecollectAsState/ SwiftUI@EnvironmentObject
SwiftUI์ ObservableObject์ ๊ตฌ์กฐ๊ฐ ๊ฐ์ฅ ๋น์ทํ๋ค. ๋ค๋ง SwiftUI๋ @Published๊ฐ ์๋ฆผ์ ์๋์ผ๋ก ๋ณด๋ด๋ ๋ฐ๋ฉด, Flutter๋ notifyListeners()๋ฅผ ์ง์ ๋ถ๋ฌ์ผ ํ๋ค. ์ด ํธ์ถ์ ๋นผ๋จน๋ ๊ฒ Flutter์์ ๊ฐ์ฅ ํํ ์ค์๋ค.
์ ๋ฆฌ
- ํ๋ฒํ ํด๋์ค๋ ๊ฐ์ด ๋ฐ๋์ด๋ ์๋ ค์ฃผ์ง ์์์ ํ๋ฉด์ด ๊ฐฑ์ ๋์ง ์๋๋ค
ChangeNotifier๋ฅผ ์์ํ๊ณ ๊ฐ์ ๋ฐ๊พผ ๋คnotifyListeners()๋ฅผ ํธ์ถํ๋ฉด, ๊ทธ ๊ฐ์ ๊ตฌ๋ ํ ์์ ฏ๋ค์ด ๋ค์ ๊ทธ๋ ค์ง๋ค- ํ๋ฉด์ ๋ณด์ฌ์ค ๊ฐ์
build์์์context.watch, ๋ฒํผ ๊ฐ์ ๋์์ ์ฝ๋ฐฑ ์์์context.read๋ฅผ ์ด๋ค - Provider๋ฅผ ๊ฑธ์ด๋
build์์ ๋ฐ๋ก ์ฝ์ผ๋ฉดProviderNotFoundException์ด ๋๋ฏ๋ก, ์ฝ๋ ์ชฝ์ ๋ณ๋ ์์ ฏ์ด๋Consumer๋ก ๋ถ๋ฆฌํ๋ค - ์ฌ๋ฌ ๋ชจ๋ธ์
MultiProvider๋ก ์ฑ ๋งจ ์์ ๊ฑธ๊ณ , ์ผ๋ถ๋ง ๊ตฌ๋ ํ๋ ค๋ฉดselect๋ฅผ ์ด๋ค - Provider๋
InheritedWidget์ ๊ฐ์ผ ํจํค์ง๋ผ์, ์ง๋ ๊ธ์ ์๋ฆฌ๊ฐ ๊ทธ๋๋ก ๋ค์ด ์๋ค
๋ค์ ๊ธ์์๋ Provider์ ํ๊ณ์, ์ด๋ฅผ ๋ณด์ํ Riverpod์ ๋ค๋ค๋ณด๊ฒ ๋ค.
