HOME

Riverpod

1. 동일한 타입의 Provider를 여러 개 선언할 수 없음

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(), ), ); }
Dart
복사
이 경우 context.read<Item>()을 호출하면 항상 두 번째 Provider<Item>을 참조합니다. Riverpod에서는 이 문제를 전역 변수를 사용해 간단하게 해결할 수 있습니다.
final itemAProvider = Provider<Item>((ref) => Item('Item A')); final itemBProvider = Provider<Item>((ref) => Item('Item B'));
Dart
복사
이렇게 하면 각각의 Provider를 명확히 구분하여 사용할 수 있습니다.

2. 여러 상태(값) 관리의 어려움

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(); } } }
Dart
복사
위 예제에서는 로딩 상태를 나타내는 isLoading 플래그를 추가하여 로딩 상태와 데이터를 관리하고 있지만, 코드가 복잡해집니다. RiverpodAsyncValue API는 로딩, 에러, 데이터 상태를 자동으로 처리합니다.
final itemsProvider = FutureProvider<List<Item>>((ref) async { final result = await fetchItems(); return result; });
Dart
복사
이렇게 하면 간단하게 로딩 중 상태와 에러, 데이터를 함께 관리할 수 있습니다.

3. 결합의 복잡성

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(), ), ); }
Dart
복사
이렇게 ProxyProvider는 상태 결합이 복잡하고 의도치 않은 상태 변화가 발생할 가능성이 큽니다. 반면, Riverpod에서는 ref.watchref.listen으로 상태를 자연스럽고 간단하게 결합할 수 있습니다.

4. 안정성 부족

Provider는 런타임에 참조를 찾기 때문에 리팩토링 중 Provider 정의가 누락되거나 참조가 변경되면 ProviderNotFoundException 오류가 발생할 수 있습니다. 예를 들어, 아래와 같은 경우 Provider 정의가 누락되면 예외가 발생합니다.
final userProvider = Provider<User>((context) => throw UnimplementedError()); void main() { runApp( MultiProvider( providers: [ Provider<User>(create: (_) => User('Test User')), // 중복 정의로 예외 가능성 ], child: MyApp(), ), ); }
Dart
복사
반면, Riverpod은 전역 상태로 Provider를 정의하여 이러한 예외가 발생하지 않습니다. 코드의 안정성이 높아지고 유지보수가 쉬워집니다.

5. 상태 해제의 어려움

Provider는 상태 해제를 자동으로 처리하지 않아 페이지 간 상태 공유 시 수동으로 관리해야 합니다. 또한 더 이상 사용되지 않는 상태를 해제하기 어렵습니다.
ChangeNotifierProvider<MyNotifier>( create: (context) => MyNotifier(), dispose: (context, notifier) => notifier.dispose(), )
Dart
복사
반면 Riverpod.autoDispose를 통해 상태 해제를 자동으로 처리할 수 있습니다.
final myProvider = Provider.autoDispose<int>((ref) => 42);
Dart
복사
.autoDispose를 사용하면 더 이상 사용되지 않는 상태가 자동으로 해제되어 메모리 관리가 용이해집니다.

6. 안정적인 파라미터 관리의 어려움

Provider는 .family와 같은 파라미터화 기능이 없어 파라미터를 기준으로 상태를 관리하기 어려움이 있습니다. 파라미터별 상태를 구분하기 위해 중간 위젯을 추가하거나 별도의 로직이 필요합니다.
final userProvider = Provider<User>((context) => User('default')); // 특정 ID에 따른 상태 관리가 어려움
Dart
복사
Riverpod에서는 .family를 통해 파라미터에 따른 상태를 쉽게 관리할 수 있습니다.
final userProvider = Provider.family<User, String>((ref, userId) { return fetchUser(userId); });
Dart
복사
.family를 사용하면 파라미터에 따라 상태를 분리하고, 파라미터가 다른 상태를 효율적으로 관리할 수 있습니다.

결론

Provider는 간단한 상태 관리를 위해 사용될 수 있지만, 동일 타입 Provider 관리, 로딩 및 여러 상태 관리, 상태 결합의 복잡성, 안정성 부족, 상태 해제 어려움, 파라미터화 제약 등 여러 한계가 있습니다. 반면, Riverpod은 전역 상태 관리와 추가적인 기능들을 통해 보다 유연하고 안정적인 상태 관리가 가능해 다양한 상황에서 더 적합한 선택이 될 수 있습니다.