1편에서는 Riverpod의 설치 방법과 기본적인 정의 및 사용 방법에 대해 알아봤습니다.
2편에서는 동적인 값을 제공하는 Provider를 만들어보고 이를 사용하는 방법에 대해 알아보겠습니다.
동적인 값을 제공하는 Provider 만들기
1편에서 provider의 폴더 안에 meals_providers.dart를 만들어서 provider 값을 제공했습니다.
이번에는 똑같이 provider 폴더 안에 다른 파일을 만들어보겠습니다.
그리고 다음과 같이 코드를 작성합니다.
// Meal은 임의로 설정한 클래스임
// 동적 Provider는 StateNotifier를 사용하여 정의할 수 있음
class FavoriteMealsNotifier extends StateNotifier<List<Meal>> {
// FavoriteMealsNotifier의 생성자를 빈 리스트([])로 초기화한다는 의미
FavoriteMealsNotifier() : super([]);
void toggleMealFavoriteStatus(Meal meal) {
}
}
위 코드를 보면 아래와 같은 부분이 존재하는데
FavoriteMealsNotifier() : super([]);
super([])는 부모 클래스인 StateNotifier의 생성자를 호출하며 해당 생성자의 초기 상태를 빈 리스트([])로 전달한다는 뜻이다.
Provider에 데이터를 넣는 로직 만들기
Provider를 사용하여 데이터를 정의할 때는 항상 새로운 데이터를 넣는 방식으로 진행해야 한다.
예를 들면 List 형태의 데이터를 Provider로 지정할 경우 add(), remove() 같은 함수를 사용할 수 없다.
그 이유는
StateNotifier는 메모리에서 기존 값을 수정할 수 없고 값을 변경하면 새로운 메모리에 값을 새롭게 지정한다
즉, 불변성을 가진다.
class FavoriteMealsNotifier extends StateNotifier<List<Meal>> {
// FavoriteMealsNotifier의 생성자를 빈 리스트([])로 초기화한다는 의미
FavoriteMealsNotifier() : super([]);
void toggleMealFavoriteStatus(Meal toggledMeal) {
// 클릭한 Meal이 favorite 안에 있는 Meal인지 확인
final mealIsFavorite = state.contains(toggledMeal);
// state에 넣는 값은 항상 새롭게 넣어서 갱신해야한다.
if (mealIsFavorite) {
// 클릭한 Meal 이외의 것들만 state에 넣는다 = 즉, 클릭한 Meal을 뺀다.
state = state.where((meal) => meal.id != toggledMeal.id).toList();
} else {
// 원래 state를 그대로 새롭게 List를 만들고 + 클릭했던 Meal도 추가해서 다시 state에 넣는다
// 즉, 클릭한 Meal을 넣는다.
state = [...state, toggledMeal];
}
}
}
그렇기 때문에 위와 같이 state를 사용해서 데이터를 갱신할 수 있다.
그리고 다음과 같은 값으로 정의하여 해당 Provider(제공자)를 초기화하고 접근할 수 있다.
// StateNotifierProvider에서는 어떤 데이터를 반환해주는지 명시해야한다.
// StateNotifierProvider<X, Y>에서 X는 StateNotifier 또는 해당 하위 클래스, Y는 관리하는 상태 타입을 나타낸다.
final favoriteMealsProvider = StateNotifierProvider<FavoriteMealsNotifier, List<Meal>>((ref) {
return FavoriteMealsNotifier();
});
그리고 favorites_provider.dart의 전체 코드는 다음과 같이 된다.
import 'package:flutter_architecture/examples/meals_recipe/models/meal.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class FavoriteMealsNotifier extends StateNotifier<List<Meal>> {
// FavoriteMealsNotifier의 생성자를 빈 리스트([])로 초기화한다는 의미
FavoriteMealsNotifier() : super([]);
// Provider에 있는 값을 어떻게 변환 시키길지 정의
// 여기선 클릭한 값을 List에 넣고 빼고 하는 작업을 한다
void toggleMealFavoriteStatus(Meal toggledMeal) {
// 클릭한 Meal이 favorite 안에 있는 Meal인지 확인
final mealIsFavorite = state.contains(toggledMeal);
// state에 넣는 값은 항상 새롭게 넣어서 갱신해야한다.
if (mealIsFavorite) {
// 클릭한 Meal 이외의 것들만 state에 넣는다
state = state.where((meal) => meal.id != toggledMeal.id).toList();
} else {
// 원래 state를 그대로 새롭게 List를 만들고 + 클릭했던 Meal도 추가해서 다시 state에 넣는다
state = [...state, toggledMeal];
}
}
}
/*
* StateNotifierProvider에서는 어떤 데이터를 반환해주는지 명시해야한다.
* StateNotifierProvider<X, Y>에서 X는 StateNotifier 또는 해당 하위 클래스, Y는 관리하는 상태 타입을 나타낸다.
* fianl로 정의했기 때문에 favoriteMealsProvider 자체는 바꿀 수 없지만 FavoriteMealsNotifier의 상태는 변경할 수 있습니다.
* (= state에 새로운 값을 넣음으로써 변경 가능)
* 즉, Provider 객체는 불변이지만, 그 안에 포함된 상태는 변경 가능
* */
final favoriteMealsProvider = StateNotifierProvider<FavoriteMealsNotifier, List<Meal>>((ref) {
return FavoriteMealsNotifier();
});
StateNotifier 사용하기
위에서 정의한 StateNotifier를 어떻게 사용하는지 알아보겠습니다.
해당 Provider를 사용하여 데이터를 제공해야 하는 곳에 다음과 같이 정의합니다.
물론 ref.watch()를 사용하기 위해선 ConsumerState나 ConsumerWidget으로 된 UI에서만 가능합니다.
import 'package:flutter_architecture/provider/favorites_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// riverpod은 자동으로 Notifier 클래스에 있는 Provider에서 state를 꺼내준다.
// 그렇기 때문에 favoriteMeals는 Provider에서 지정한 List<Meal>이 된다.
final favoriteMeals = ref.watch(favoriteMealsProvider);
이제 해당 Provider를 참조하고 있으니 toggleMealFavoriteStatus를 사용하여 해당 값을 갱신하면 되겠죠?
다음과 같은 방법으로 할 수 있습니다.
똑같이 ConsumerState나 ConsumerWidget으로 된 UI에서 다음과 같이 정의합니다.
// StateNotifierProvider에 notifier를 사용하면 상위 클래스인 StateNotifier에 접근 권한을 얻을 수 있다.
// 즉, 위에서 정의한 FavoriteMealsNotifier 클래스에 접근할 수 있음
ref.read(favoriteMealsProvider.notifier).toggleMealFavoriteStatus(meal);
위와 같은 방법으로 동적으로 Provider의 값을 관리하고 참조할 수 있습니다.
ref.watch()와 ref.read()의 차이점은 뭘까?
지금까지 저희는 ref에서 두 개의 메서드를 사용했습니다.
하나는 watch() 메서드 다른 하나는 read() 메서드입니다.
먼저 이 둘의 공통점은 둘 다 모두 Provider의 값을 읽어오는 데 사용되는 메서드라는 것입니다.
하지만 ref.watch() 메서드의 경우 Widget에서 Provider의 상태를 감시하는 데 사용됩니다.
즉, 특정 Provider의 상태가 변경되면 ref.watch()를 사용한 Widget(=ConsumerStatefulWidget)은 자동으로 다시 빌드됩니다.
반대로 ref.read() 메서드의 경우 Widget의 다시 빌드 없이 Provider의 상태를 읽어올 때 사용됩니다.
즉, ref.read()는 상태 값을 읽기만 하고, 상태 변화에 Widget(=ConsumerWidget)을 다시 빌드하지 않습니다.
https://android-developer.tistory.com/90
'플러터(flutter)' 카테고리의 다른 글
플러터의 AnimatedBuilder를 사용하여 Animation 만들기 - 2 (0) | 2023.12.20 |
---|---|
플러터의 AnimatedBuilder를 사용하여 Animation 만들기 - 1 (1) | 2023.12.11 |
플러터의 Riverpod 사용 방법 기초부터 자세히 설명 - 1 (2) | 2023.12.06 |
플러터(Flutter)에서 Widget UI에 있는 Key란 무엇인가? (0) | 2023.09.25 |
플러터 - UI 업데이트를 최적화 하는 방법, UI Tree란? (0) | 2023.09.22 |
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
댓글