Provider는 InheritedWidget
의 제약으로 인해 동일한 타입의 Provider<T>
를 여러 개 선언할 수 없습니다. InheritedWidget
은 가장 가까운 상위 인스턴스만 참조하기 때문에, 동일한 타입의 여러 Provider가 위젯 트리에 있으면 context.watch<T>()
나 context.read<T>()
가 항상 가장 가까운 Provider 인스턴스만 읽게 됩니다.
예를 들어, 아래와 같이 두 개의 Provider<Item>
을 선언한 경우 가장 가까운 Provider<Item>
만 참조하게 됩니다.
class Item {
final String name;
Item(this.name);
}
void main() {
runApp(
MultiProvider(
providers: [
Provider<Item>(create: (_) => Item('Item A')), // 첫 번째 Item Provider
Provider<Item>(create: (_) => Item('Item B')), // 두 번째 Item Provider
],
child: MyApp(),
),
);
}
이 경우 context.read<Item>()
을 호출하면 항상 두 번째 Provider<Item>
을 참조합니다. Riverpod에서는 이 문제를 전역 변수를 사용해 간단하게 해결할 수 있습니다.
final itemAProvider = Provider<Item>((ref) => Item('Item A'));
final itemBProvider = Provider<Item>((ref) => Item('Item B'));
이렇게 하면 각각의 Provider를 명확히 구분하여 사용할 수 있습니다.
Provider는 로딩 상태와 기존 데이터를 동시에 관리하기 어렵습니다. 새로운 데이터가 로드될 때 이전 값을 덮어쓰기 때문에, 로딩 상태와 데이터를 수동으로 관리해야 합니다. 예를 들어, 로딩 상태와 에러 상태를 처리하기 위해 추가적인 코드가 필요합니다.
class ItemsNotifier extends ChangeNotifier {
List<Item> items = [];
bool isLoading = true;
void fetchData() async {
isLoading = true;
notifyListeners();
try {
// 비동기 데이터 가져오기 (예: API 호출)
items = await fetchItems();
} catch (e) {
items = [Item('Error Item')];
} finally {
isLoading = false;
notifyListeners();
}
}
}
위 예제에서는 로딩 상태를 나타내는 isLoading
플래그를 추가하여 로딩 상태와 데이터를 관리하고 있지만, 코드가 복잡해집니다. Riverpod의 AsyncValue
API는 로딩, 에러, 데이터 상태를 자동으로 처리합니다.
final itemsProvider = FutureProvider<List<Item>>((ref) async {
final result = await fetchItems();
return result;
});
이렇게 하면 간단하게 로딩 중 상태와 에러, 데이터를 함께 관리할 수 있습니다.
Provider에서 여러 상태를 결합하려면 ProxyProvider
를 사용해야 하는데, 이는 번거롭고 오류 발생 가능성이 큽니다. 예를 들어, context.watch
를 사용해 상태를 결합할 경우 불필요한 didChangeDependencies
호출이 발생할 수 있습니다.
class UserIdNotifier extends ChangeNotifier {
String? userId;
}
class UserNameNotifier extends ChangeNotifier {
String? userName;
void setUserId(String? userId) {
if (userId != null) {
userName = 'User: $userId';
}
}
}
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<UserIdNotifier>(create: (_) => UserIdNotifier()),
ChangeNotifierProxyProvider<UserIdNotifier, UserNameNotifier>(
create: (_) => UserNameNotifier(),
update: (context, userIdNotifier, userNameNotifier) {
userNameNotifier!.setUserId(userIdNotifier.userId);
return userNameNotifier;
},
),
],
child: MyApp(),
),
);
}
이렇게 ProxyProvider
는 상태 결합이 복잡하고 의도치 않은 상태 변화가 발생할 가능성이 큽니다. 반면, Riverpod에서는 ref.watch
와 ref.listen
으로 상태를 자연스럽고 간단하게 결합할 수 있습니다.