여러 컴포넌트간 데이터전달과 이벤트 발생을 한 공간에서 관리하는 것

즐겨찾기 기능에 관련된 FavoriteState는 TabsScreen, CategoriesScreen, MealsScreen 등 다양한 스크린과 위젯에서 활용되는 상황, 각 스크린에 onToggleFavorite으로 함수를 전달하는 것은 비효율적
=> 한 공간에서 이를 뿌려주자
https://engineering.linecorp.com/ko/blog/flutter-architecture-getx-bloc-provider
Flutter 상태관리 라이브러리 3대장을 잘 비교해주신 글.
Provider 의 Anagram.
[공식 Docs 페이지] https://riverpod.dev/ko/docs/introduction/why_riverpod
공급자(Provider)는 소비자(Cumstomer)의 관계기반,
소비자위젯에서 메서드를 호출하면 공급자에서 데이터를 뿌려주어 UI를 변화시킴.
모든 위젯은 공급자에 연결이 가능하다
root of Application 을 ProviderScope로 감싸주어야 한다
void main() {
runApp(
const ProviderScope(
child: App(),
),
);
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:meals/data/dummy_data.dart';
final mealsProvider = Provider((ref) {
return dummyMeals;
});
위에 코드 스니펫처럼 Provider 함수는 반드시 ref 를 파라미터로 가져야한다.
ref 객체는 프로바이더와, 컨슈머간 상호작용을 담당
1. ref.watch: 프로바이더의 값을 취득하고 변화를 구독. 값의 변경이 발생하면 위젯을 다시 빌드하거나, 값을 구독하고 있는 위체에 상태 값을 전달 및 제공
2. ref.listen: 프로바이더의 상태 값을 구독하거나, 상태값이 변했을때 어떤 행위를 취해야할 경우사용
3. ref.read : 프로바이더의 상태값을 취득, 이벤트 콜백함수에 사용하는데 유용
💡 ref.read와 ref.listen 보단 ref.watch 사용을 권장
두 메서드의 차이는 위젯 재빌드의 여부, 즉 UI 업데이트를 통해 화면을 다시그려야 한다면 ref.watch를, 특정 함수만 호출하고 싶다면 ref.listen. 또한 비동기 처리가 필요한 환경이라면 ref.watch만 가능하다.
📚 아직 ref.read와 ref.watch 의 차이점이 명확하지 않다...
class FavoriteMealsNotifier extends StateNotifier<List<Meal>> {
FavoriteMealsNotifier() : super([]); // 빈 리스트로 초기화
void toggleMealFavoriteStatus(Meal meal) {
// 즐겨찾기 부분
final mealIsFavoirte = state.contains(meal);
if (mealIsFavoirte) {
state = state.where((m) => m.id != meal.id).toList();
} else {
state = [];
}
state = [...state, meal];
}
}
final favoriteMealsProvider =
StateNotifierProvider<FavoriteMealsNotifier, List<Meal>>((ref) {
return FavoriteMealsNotifier();
});
// meal_details.dart
Widget build(BuildContext context, WidgetRef ref) {
final favoriteMeals = ref.watch(favoriteMealsProvider);
final isFavorite = favoriteMeals.contains(meal);
// tabs.dart
Widget build(BuildContext context) {
final availableMeals = ref.watch(filteredMealsProvider);
Widget activePage = CategoriesScreen(
availableMeals: availableMeals,
);
var activePageTitle = 'Categories';
if (_selectedPageIndex == 1) {
final favoriteMeals = ref.watch(favoriteMealsProvider);
activePage = MealsScreen(
meals: favoriteMeals,
);
activePageTitle = 'Your Favorites';
}
만약 특정 상황에서 데이터가 바뀐다면?
Notifiter 를 활용하는 것이 요구됨
위 코드 스니펫일 경우, 즐겨찾기 상태를 관리하는 FavoriteMealsNotifier 클래스를 생성, 제네릭(List<Meal>)에 관리할 상태를 명시
final filteredMealsProvider = Provider((ref) {
final meals = ref.watch(mealsProvider);
final activeFilters = ref.watch(filtersProvider);
return meals.where((meal) {
if (activeFilters[Filter.glutenFree]! && !meal.isGlutenFree) {
return false;
}
if (activeFilters[Filter.lactoseFree]! && !meal.isLactoseFree) {
return false;
}
if (activeFilters[Filter.vegetarian]! && !meal.isVegetarian) {
return false;
}
if (activeFilters[Filter.vegan]! && !meal.isVegan) {
return false;
}
return true;
}).toList();
같은 Provider 내부에 다중 공급자 설정이 가능,
위 코드 스니펫에선 mealsProvider 와 filtersProvider 로부터 데이터를 공급받음
음식 정보(dummyMeals -> meals)와 음식 카테고리(Filter -> activeFilters)를 통해서 필터 기능 구현