이어서 정리해보겠다.
Riverpod에서 family modifier는 특정 인수 또는 키에 따라 동적으로 상태를 생성하거나 제공하는 데 사용되는 중요한 개념 중 하나이다. 다시 말해서 family modifier를 사용하면 인스턴스 생성시 매개변수를 받아서 매개변수에 따라 다른 데이터를 받을수 있다. 코드와 결과로 보자.
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/screen/family_modifier_screen.dart';
final familyModifierProvider =
FutureProvider.family<List<int>, int>((ref, data) async {
await Future.delayed(
Duration(seconds: 2),
);
return List.generate(5, (index) => index * data);
});
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/riverpod/family_modifier_provider.dart';
class FamilyModifierScreen extends ConsumerWidget {
const FamilyModifierScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state1 = ref.watch(familyModifierProvider(3));
final state2 = ref.watch(familyModifierProvider(5));
return Scaffold(
appBar: AppBar(
title: const Text('FamilyModfierScreen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
state1.when(
data: (data) => Text(data.toString()),
error: (err, stack) => Text(err.toString()),
loading: () => const CircularProgressIndicator()),
state2.when(
data: (data) => Text(data.toString()),
error: (err, stack) => Text(err.toString()),
loading: () => const CircularProgressIndicator()),
],
),
),
);
}
}
기존 방식과는 다르게 Provider 생성시 family를 이용하여 생성하고,매개변수를 받을수 있다. 따라서 Provider를 watch할때 매개변수를 넣어주게 되면 다른 결과값을 얻을 수 있다.
일반적으로 Flutter 앱에서는 위젯이 화면에서 사라질 때 관련된 데이터나 리소스를 해제해야 한다.
Dispose는 메모리 누수를 방지하고 필요 없는 리소스를 해제하는 데 중요한 역할을 한다. AutoDisposeModifier는 modifier는 상태 관리 중에 자동으로 프로바이더를 "dispose"하는데 사용된다. 이것은 특히 페이지나 화면이 이동될 때, 위젯이 제거될 때 또는 화면이 더 이상 해당 Provider에 의존하지 않을 때 유용하다. 사용방법을 알아보자.
import 'package:flutter_riverpod/flutter_riverpod.dart';
final autoDisposeModifierProvider =
FutureProvider.autoDispose<List<int>>((ref) async {
await Future.delayed(Duration(seconds: 2));
return [1, 2, 3, 4, 5];
});
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/riverpod/auto_dispose_modifier_provider.dart';
class AutoDisposeModifierScreen extends ConsumerWidget {
const AutoDisposeModifierScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(autoDisposeModifierProvider);
return Scaffold(
appBar: AppBar(
title: Text('AutoDisposeModifierScreen'),
),
body: Center(
child: state.when(
data: (data) => Text(data.toString()),
error: (err, stack) => Text(err.toString()),
loading: () => const CircularProgressIndicator()),
));
}
}
AutoDisposeModifier는 Provider 생성시 autoDispose를 이용하여 생성을 해서 평소처럼 사용을 하게되면 자동으로 autoDispose를 해준다.
하나의 provider안에서 여러개의 provider를 사용해 보는 코드를 작성하겠다. 운동의 종류를 구분하는 기능을 구현해 볼 것이다.
우선 기능구현을 위해 기존 사용한 WorkoutModel을 수정한다.
class WorkoutModel {
final String name;
final int weight;
final bool isSuccess;
final String target;
WorkoutModel({
required this.name,
required this.weight,
required this.isSuccess,
required this.target,
});
WorkoutModel copyWith({
String? name,
int? weight,
bool? isSuccess,
String? target,
}) {
return WorkoutModel(
name: name ?? this.name,
weight: weight ?? this.weight,
isSuccess: isSuccess ?? this.isSuccess,
target: target ?? this.target);
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/model/workout_model.dart';
import 'package:flutter_riverpod_playground/riverpod/state_notifier_provider_2.dart';
final filteredWorkoutProvider = Provider<List<WorkoutModel>>((ref) {
final workoutListState = ref.watch(workoutListProvider);
final filterState = ref.watch(filterProvider);
if (filterState == FilterState.all) {
return workoutListState;
}
return workoutListState
.where((e) => filterState == FilterState.upper
? e.target == '상체'
: e.target == '하체')
.toList();
});
final filterProvider = StateProvider<FilterState>((ref) => FilterState.all);
enum FilterState { upper, lower, all }
final workoutListProvider =
StateNotifierProvider<WorkOutListNotfier, List<WorkoutModel>>(
(ref) => WorkOutListNotfier());
class WorkOutListNotfier extends StateNotifier<List<WorkoutModel>> {
WorkOutListNotfier()
: super(
[
WorkoutModel(
name: '벤치프레스', weight: 105, isSuccess: true, target: '상체'),
WorkoutModel(
name: '데드리프트', weight: 200, isSuccess: true, target: '하체'),
WorkoutModel(
name: '스쿼트', weight: 170, isSuccess: false, target: '하체'),
WorkoutModel(
name: '바벨로우', weight: 100, isSuccess: true, target: '상체'),
WorkoutModel(
name: '인클라인벤치프레스', weight: 90, isSuccess: false, target: '상체'),
WorkoutModel(
name: 'OHP', weight: 65, isSuccess: false, target: '상체'),
WorkoutModel(
name: '런지', weight: 700, isSuccess: false, target: '하체'),
],
);
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/model/workout_model.dart';
import 'package:flutter_riverpod_playground/riverpod/workout_provider.dart';
class WorkoutScreen extends ConsumerWidget {
const WorkoutScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(filteredWorkoutProvider);
return Scaffold(
appBar: AppBar(
title: Text('WorkoutScreen'),
actions: [
PopupMenuButton(
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) => Center(
child: Text(
e.name,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
)))
.toList()),
);
}
}
filteredWorkoutProvider에서는 filterProvider의 값을 바꿔주면 workoutListProvider를 filterProvider의 조건에 맞게 반환하도록 하나의 provider안에서 provider 두개를 조합하여 사용하고 있다. 적절하게 Provider를 중첩하여 사용한다면 코드를 모듈화하고 재사용성을 높이는 등 여러가지 장점을 가질 수 있다.
Riverpod 라이브러리에서 제공하는 기능 중 하나로, 상태 변화를 감지하고 이에 대한 처리를 수행하기 위한 도구이다. ProviderObserver를 사용하면 Provider의 상태가 변경될 때 특정 작업을 수행하거나, UI를 업데이트하는 등의 작업을 수행할 수 있다.
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Logger extends ProviderObserver {
@override
void didUpdateProvider(ProviderBase<Object?> provider, Object? previousValue,
Object? newValue, ProviderContainer container) {
print(
'[Provider Updated] provider : $provider / previousValue : $previousValue , newValue : $newValue ');
}
@override
void didAddProvider(ProviderBase<Object?> provider, Object? value,
ProviderContainer container) {
print('[Provider Added] provider : $provider / value : $value');
}
@override
void didDisposeProvider(
ProviderBase<Object?> provider, ProviderContainer container) {
print('[Provider Disposed] provider : $provider');
}
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/riverpod/provider_observer.dart';
import 'package:flutter_riverpod_playground/screen/home_screen.dart';
void main() {
runApp(
ProviderScope(
observers: [Logger()],
child: MaterialApp(
home: HomeScreen(),
),
),
);
}
사용목적은 주로 로그 확인을 위해 사용을 하고 사용방법은 간단하다. ProviderObserver 상속받은 클래스를 ProviderScope에 observers에 사용하면된다. Provider 추가할때,상태 업데이트할때,Provider 해제할때의 로그를 확인할 수 있다.
참고
https://www.inflearn.com/course/%ED%94%8C%EB%9F%AC%ED%84%B0-%EC%8B%A4%EC%A0%84/dashboard