플러터(2)
Riverpod
Provider
- 가장 기본 베이스가 되는 Provider
- 아무 타입이나 반환 가능
- 서비스, 계산한 값드을 반활할때 사용
- 반환값을 캐싱할때 유용하게 사용된다,
- 여러 Provider의 값들을 묶어서 사용가능
- ex) 데이터 캐싱
- 읽기만 가능하고 값 변경 불가능
final provider = Provider<int>((ref){
return 0;
});
StateProvider
- UI에서 “직접적으로” 데이터를 변경할 수 있도록 하고싶을때 사용
- 단순한 형태의 데이터만 관리 (int, double, String 등)
- Map, List등 복잡한 형태의 데이터는 다루지 않음
- 복잡한 로직이 필요한경우 사용(number++ 정도의 간단한 로직에서 사용)
- ex) 간단한 상태값 관리
final stateProvider = StateProvider<int>((ref){
return 0;
});
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(stateProvider);
return Scaffold(
body: Column(
children : [
ElevateButton(
onPressed: (){
ref.read(stateProvider.notifier).update((state) => state+1);
},
child: Text(
'증가'
)
),
ElevateButton(
onPressed: (){
ref.read(stateProvider.notifier).update((state) => state-1);
},
child: Text(
'감소'
)
)
]
)
);
}
StateNotifierProvider
- StateProvider와 마찬가지로 UO에서 “직접적으로” 데이터를 변경할 수 있도록 하고싶을때 사용
- 복잡한 형태의 데이터 관리가능 (클래스의 메소드를 이용한 상태관리)
- StateNotifier를 상속한 클래스 반환
- ex) 복잡한 상태값 관리
ShoppingItemModel
class ShoppingItemModel{
final String name;
final String quantity;
final bool hasBought;
ShoppingItemModel({
required this.name,
required this.quantity.
required this.hasBought
});
}
StateNotifierPrivoder
final shoppingListProvider = StateNotifierProvider<ShooppingListNotifier,List<ShoppingItemModel>>((ref) => ShoppingListNotifier());
class ShoppingListNotifier extends StateNotifier<List<ShoppingItemModel>>{
ShoppingListProvider():
suepr([
ShoppingItemModel(
name: '김치',
quantity: 3,
hasBought: true
),
ShoppingItemModel(
name: '라면',
quantity: 5,
hasBought: false
),
ShoppingItemModel(
name: '삼겹살',
quantity: 30,
hasBought: true
)
]);
void toggleHasBought({required String name}){
//state는 StateNotifier에 자동으로 사용 가능(super 값으로 자동 초기화)
state = state.map((e)=> e.name == name ? ShoppingModel(
name: e.name,
quantity: e.quantity,
hasBought: !e.hasBought,
)
: e);
}
}
사용예시
class StateNotifierProviderScreen extends ConsumerWidget{
@override
Widget build(BuildContext context, WidgetRef ref){
final state = ref.watch(shoppingListProvider);
return Container(
body: ListView(
children : state
.map(
(e) => CheckboxListTile(
title: Text(e.name)
value: e.hasBought
onChanged: (value){
ref.read(shoppingListProvider.notifier).toggleHasBought(name: e.name);
}
)
)
)
);
}
}
FutureProvider
- Future 타입만 반환 가능
- API 요청의 결과를 반환할때 자주 사용
- 복잡한 로직 또는 사용자의 특정 행동뒤에 Future를 재실행하는 기능이 없음 -> 필요할 경우 StateNotifierProvider 사용
- ex) API 요청의 Future 결과값
final futureProvider = FutureProvider<List<int>>((ref) async{
await future.delayed(
Duration(
seconds:2
)
);
return [1,2,3,4,5];
})
StreamProvider
- Stream 타입만 반환 가능
- API 요청의 결과를 Stream으로 반환할때 자주 사용(Socket 등)
- ex) API 요청의 Stream 결과값
Modifier
Family Modifier
//두번째 파라미터를 제너릭이랑 전달값에 넣어줌
final familyModifierProvider = FutureProvider.family<List<int>,int>((ref,data) async{
await Future.delayed(Duration(seconds:2));
return [1,2,3];
})
AutoDispose Modifier
//로딩이 한번 되고나서 다른 프로바이더는 state값을 기억하지만 autoDispose는 값을 캐싱하지 않음
final autoDisposedModifierProvider = FutureProvider.autoDispose<List<int>>((ref) {
await Future.delayed(Duration(seconds:2));
return [1,2,3];
});
listenProvider
provider
final listenProvider = StateProvider<int>((ref) => 0);
screen
//ConsumerStatefulWidget은 WidgetRef 선언하지 않아도 바로 ref 사용 가능
class ListenProviderScreen extends ConsumerStatefulWidget {
const ListenProviderScreen({Key? key}) : super(key: key);
@override
ConsumerState<ListenProviderScreen> createState() => _ListenProviderScreenState();
}
class _ListenProviderScreenState extends ConsumerState<ListenProviderScreen> with
TickerProviderStateMixin{
late final TabController controller;
@override
void initState() {
// TODO: implement initState
super.initState();
controller = TabController(length: 10, vsync: this,
initialIndex: ref.read(listenProvider));
}
@override
Widget build(BuildContext context) {
ref.listen<int>(listenProvider, (previous, next) {
if(previous!=next){
controller.animateTo(
next
);
}
});
return DefaultLayout(
title: 'ListenProviderScreen',
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: controller,
children: List.generate(
10,
(index) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(index.toString()),
ElevatedButton(
onPressed: (){
ref.read(listenProvider.notifier).update((state) => state == 10 ? 10 : state+1);
},
child: Text('다음')),
ElevatedButton(
onPressed: (){
ref.read(listenProvider.notifier).update((state) => state == 0 ? 0 : state-1);
},
child: Text('뒤로')),
],
)),
)
);
}
}
selectProvider
//isSpicy값만 변경 됐을때 리빌드, state가 isSpicy의 값으로 반환
final state = ref.watch(selectProvider.select((value) => value.isSpicy));
ref.listen(selectProvider.select((value) => value.hasBought),
(previous, next) {
}
);
Provider 안에 Provider 사용하기
screen
class ProviderScreen extends ConsumerWidget {
const ProviderScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context,WidgetRef ref) {
final state = ref.watch(filteredShoppingListProvider);
return DefaultLayout(title: 'ProviderScreen',
actions: [
PopupMenuButton<FilterState>(
itemBuilder: (_) => FilterState.values.map(
(e) => PopupMenuItem(
value: e,
child: Text(e.name)
)
).toList(),
onSelected: (value){
ref.read(filterProvider.notifier).update((state) => value);
},
)
],
body: ListView(
children:
state.map((e) => CheckboxListTile(
title: Text(e.name),
value: e.hasBought,
onChanged: (value){
ref.read(shoppingListProvider.notifier).toggleHasBought(name: e.name);
},
),
).toList(),
)
);
}
}
provider
final filteredShoppingListProvider = Provider<List<ShoppingItemModel>>(
(ref) {
final filterState = ref.watch(filterProvider);
final shoppingListState = ref.watch(shoppingListProvider);
if(filterState == FilterState.all){
return shoppingListState;
}
return shoppingListState.where((element) =>
filterState == FilterState.spicy
? element.isSpicy
: !element.isSpicy
).toList();
}
);
enum FilterState{
notSpicy,
spicy,
all
}
final filterProvider = StateProvider<FilterState>((ref) => FilterState.all);
ProviderObserver - 로그 용도로 사용
main.dart
void main() {
runApp(
ProviderScope(
observers: [
Logger(),
],
child: MaterialApp(
home: HomeScreen(),
),
)
);
}
provider
class Logger extends ProviderObserver{
//하위에 있는 provider를 업데이트 했을 때 무조건 이 코드가 불림
@override
void didUpdateProvider(ProviderBase<Object?> provider, Object? previousValue,
Object? newValue, ProviderContainer container) {
print('[Provider Update] provider: $provider / pv: $previousValue / nv: $newValue' );
}
//프로바이더를 추가했을 때
@override
void didAddProvider(ProviderBase<Object?> provider, Object? value, ProviderContainer container) {
print('[Provider Added] provider: $provider / value: $value' );
}
//프로바이더를 삭제했을 때(autoDisposed provider)
@override
void didDisposeProvider(ProviderBase<Object?> provider, ProviderContainer container) {
print('[Provider Dispose] provider: $provider' );
}
}
ref.read vs ref.watch
- ref.watch는 반환값의 업데이트가 있을때 지속적으로 build 함수를 다시 실행해준다.
- ref.watch는 필수적으로 UI관련 코드에만 사용한다.
- ref.read는 실행되는 순간 단 한번만 provider 값을 가져온다.
- ref.read는 onPressed 콜백처럼 특정 액션 뒤에 실해되는 함수 내부에서 사용된다.
Leave a comment