Provider란?
Provider는 상태 관리를 쉽게 하게 해준다. 여러 화면에서 같은 상태를 공유할 수 있고, 상태가 변경되면 UI를 자동으로 업데이트한다.
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 상태 변경 알림
}
}
ChangeNotifier
상태가 변경될 때 UI에 알림을 보내 다시 그리도록 한다.
class MyModel extends ChangeNotifier {
int value = 0;
void increment() {
value++;
notifyListeners(); // UI에 상태가 변경되었음을 알림
}
}
ChangeNotifierProvider
ChangeNotifier
를 앱에서 사용할 수 있도록 해주는 도구다.
ChangeNotifierProvider(
create: (_) => MyModel(),
child: MyApp(),
);
MultiProvider
여러 상태를 다루고 싶을 때 사용한다.
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MyModel1()),
ChangeNotifierProvider(create: (_) => MyModel2()),
],
child: MyApp(),
);
Provider.of(context)
Provider에 저장된 상태를 가져올 때 사용한다.
int value = Provider.of<MyModel>(context).value;
Consumer
상태가 변경될 때 해당 부분만 다시 그리도록 도와준다.
Consumer<MyModel>(
builder: (context, model, child) {
return Text(model.value.toString());
},
);
context.watch()
상태를 구독하고, 변경이 발생하면 UI를 다시 그린다.
int value = context.watch<MyModel>().value;
context.read()
상태를 한 번만 가져오고, 이후 상태 변경을 감지하지 않는다.
context.read<MyModel>().increment();
notifyListeners()
상태가 변경되면 구독하는 모든 위젯에 알림을 보낸다.
void increment() {
value++;
notifyListeners(); // 구독 중인 모든 위젯에 알림
}
성능 최적화
Consumer
나 Selector
를 사용해 필요한 부분만 다시 그리도록 하여 성능을 최적화한다.
Consumer<MyModel>(
builder: (context, model, child) {
return Text(model.value.toString());
},
);
이 코드들을 통해 상태를 효율적으로 관리하고 성능을 최적화할 수 있다.
더 잘 이해하기 위해 버스가 도착하는 시간을 알려주는 버스알림앱을 예로 들어보도록 하자
코드 예시 1)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'bus_view_model.dart';
import 'user_favorite_bus_view_model.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Provider를 사용해 BusViewModel과 UserFavoriteBusViewModel을 주입받음
final busViewModel = Provider.of<BusViewModel>(context);
final favoriteBusViewModel = Provider.of<UserFavoriteBusViewModel>(context);
return Scaffold(
appBar: AppBar(title: Text('즐겨찾기한 버스')),
body: Column(
children: [
// 버튼 클릭 시 BusViewModel의 fetchBusStops() 호출 -> API 호출
ElevatedButton(
onPressed: () {
busViewModel.fetchBusStops(); // API 호출
},
child: Text('버스 도착 정보 불러오기'),
),
// 즐겨찾기된 버스 리스트 출력
Expanded(
child: ListView.builder(
itemCount: favoriteBusViewModel.favoriteBuses.length,
itemBuilder: (context, index) {
final bus = favoriteBusViewModel.favoriteBuses[index];
return ListTile(
title: Text('${bus.busNumber}번 버스 - ${bus.stopName}'),
subtitle: Text('도착 시간: ${bus.arrivalTime}'),
// 즐겨찾기 삭제 버튼
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
favoriteBusViewModel.removeFavoriteBus(bus); // 즐겨찾기 삭제
},
),
);
},
),
),
],
),
);
}
}
- Provider 주입: Provider.of<T>(context)를 사용해 BusViewModel과 UserFavoriteBusViewModel을 각각 주입받음.
- API 호출: 버튼 클릭 시 busViewModel.fetchBusStops()를 호출해 버스 도착 정보를 가져오는 API 호출.
- 즐겨찾기 목록: favoriteBusViewModel.favoriteBuses에서 즐겨찾기된 버스 리스트를 가져와 화면에 표시.
- 즐겨찾기 삭제: 삭제 버튼 클릭 시 favoriteBusViewModel.removeFavoriteBus(bus)로 즐겨찾기된 버스를 삭제.
2) 메인의 코드를 봐보자
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'bus_view_model.dart';
import 'user_favorite_bus_view_model.dart';
import 'home_screen.dart';
import 'bus_list_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// MultiProvider로 여러 ViewModel을 제공
return MultiProvider(
providers: [
// BusViewModel을 주입하여 상태 관리
ChangeNotifierProvider(create: (_) => BusViewModel()),
// UserFavoriteBusViewModel을 주입하여 즐겨찾기 상태 관리
ChangeNotifierProvider(create: (_) => UserFavoriteBusViewModel()),
],
child: MaterialApp(
title: 'Bus API Example',
home: HomeScreen(), // 초기 화면을 HomeScreen으로 설정
routes: {
'/bus-list': (context) => BusListScreen(), // '/bus-list' 경로 설정
},
),
);
}
}
- MultiProvider: BusViewModel과 UserFavoriteBusViewModel 두 개의 상태(ViewModel)를 제공.
- ChangeNotifierProvider: 각 ViewModel을 ChangeNotifierProvider로 주입하여 상태 관리.
- MaterialApp: 앱의 초기 화면을 HomeScreen으로 설정하고, /bus-list 경로를 설정해 화면 간 이동 가능하게 함.
궁금증, 프로바이더가 MVVM에 쓰이는 기본 상태관리 툴이라고 했는데, 그렇다면 ViewModel 자체에 프로바이더를 씌우는게 당연한가?
< 이야기를 나눠 본 결과 >
보통은 그렇게 쓴다고 한다. 보통 RxSwift에서 쓰는 형태로 생각하면 될 것 같다. 상태는 보통 Data라고 생각해 상태관리하면 그 안에 있는 데이터만 바뀌어야한다고 생각했다. 편협한 생각이었을지도. MVVM에 안맞는 생각이었던거같은데, 좀 더 고민해볼 여지가 있는듯하다.
댓글로 다르게 생각하는, 혹시 첨언을 해주실 여러분의 의견을 정말 환영하며
'Flutter > Flutter' 카테고리의 다른 글
플러터 프로젝트에서 DI 주입은 어떻게 이루어지는가 (0) | 2024.09.19 |
---|---|
1. 앱에 어울리는 애니메이션 구현하기 <Lottie편> (1) | 2024.09.16 |
mainColor를 const로 고정해주고 싶었을 뿐인데 (0) | 2024.08.30 |
위젯 만들 때 마다 계속 나오는 @override와 super.key는 뭐지? (2) | 2024.08.26 |
왜 자꾸 const를 쓰라고 하는거지? (0) | 2024.08.26 |