
Riverpod. Provider 패키지와 동일한 개발자가 만든 상태 관리 패키지이다.
기존에 회사에서는 상태 관리를 위해 Provider를 사용하고 있었다. 당시에는 편리하게 사용하고 있었지만 가끔씩 문제가 발생할 때마다 난감했는데, Riverpod의 경우 그런 문제들을 해결했다. 다만, 기존의 Provider와 비교해보면 많이 달라졌기에, 그 차이점들을 하나하나 짚어 가려 한다.
Riverpod에서 Provider로 변경되면서 많은 점이 변경되었다. 달라진 점을 하나씩 짚어보려고 한다.
기존의 Provider는 위젯이었다. 그래서 위젯 트리 안에 배치 되었다. 하지만 Riverpod에서 Provider는 Dart 오브젝트다.
// Provider 패키지의 Counter 프로바이더
class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}
// Riverpod 패키지의 Counter 프로바이더
final counterProvider = StateProvider((ref) => 0);
엄청 간단해졌다.
Provider 에서는 프로바이더를 만들 때마다 등록해줘야 했다. 보통 가장 최상단에 MultiProvider로 프로바이더를 등록했는데, 깜빡했을 경우 런타임에서 오류를 확인해야 했다. 그 오류는 이렇게
반면 Riverpod은 편리해졌다. 앱의 최상단을 ProviderScope로 감싸주기만 하면된다.
// Provider
void main() {
  runApp(
    MultiProvider(
      providers: [
        // 프로바이더를 등록하지 않으면, 오류가 발생한다
        ChangeNotifierProvider<Something>(create: (context) => Something()),
      ],
      child: const MyApp(),
    ),
  );
}
// Riverpod
void main() {
  // ProviderScope로 감싸주기만 하면 된다.
  runApp(ProviderScope(child: const MyApp()));
}
Provider는 BuildContext를 이용해 값을 읽고, Riverpod은 WidgetRef를 이용해 값을 읽는다. Riverpod에서 달라진 점이라면 기존에 사용하던 StatelessWidget, StatefulWidget 대신 ConsumerWidget, ConsumerStatefulWidget을 사용한다는 것이다. ConsumerWidget이 StatelessWidget과 다른 점은 build 함수에 WidgetRef라는 매개변수가 추가된다는 것이다. Riverpod은 이 WidgetRef를 이용한다.
Riverpod의 편리한 점은 여기서 나온다. Provider는 런타임 전까지는 오류를 확인할 수 없으나, Riverpod은 컴파일 시점에서 오류를 확인할 수 있다.
// Provider
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('${context.watch<Counter>().count}')), //  BuildContext에 해당 프로바이더가 등록되어 있지 않더라도, 코드에서는 오류를 확인할 수 없다.
    );
  }
}
// Riverpod
class MyHomePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) { // BuildContext 외에 WidgetRef 도 추가되었다.
    return Scaffold(
      body: Center(
        child: Consumer(
          builder: (context, ref, child) {
            // 값의 관찰이 필요한 곳에서 ref.watch 를 이용해서 counterProvider 값의 변경을 관찰한다.
            // counterProvider가 정의되어 있지 않다면, 코드는 counterProvider를 찾을 수 없다는 오류를 알려준다.
            final count = ref.watch(counterProvider);
            return Text('$count');
          },
        ),
      ),
    );
  }
}
Riverpod은 프로바이더를 선언하지 않았다면 컴파일 시점에서 에러를 내뱉는다. 미리 오류를 체크하고 방지할 수 있다.
그렇다면 Riverpod이 현재 가장 좋은 상태 관리 패키지라고 할 수 있는가? 이 부분에선 의견이 갈린다. GetX, Provider, Bloc 등의 상태 관리 패키지가 있으며, 사람들은 자기가 쓰는 패키지의 장점을 내세우며, 다른 패키지의 단점을 언급한다. GetX는 Flutter의 작동 방식을 무시하고 있으며, Provider는 런타임 시 발생하는 오류를 찾기 어려우며, Bloc은 너무 복잡하다고 말한다.
Riverpod에 대해서도 말이 나온다. 순수한 Dart 오브젝트이기에 테스트가 편리하지만 Flutter의 BuildContext를 사용하지 않기 때문에 사람들이 해당 개념을 익히기 쉽지 않고, Flutter의 생태계가 아닌 Riverpod의 생태계를 익히도록 만든다는 비판이 있다.
결국 완벽한 것은 없기에, 어떤 상태 관리 패키지를 사용하느냐는 개발자가 스스로 결정해야할 것 같다. 나의 경우엔 이미 Provider의 런타임 에러와 GetX의 느린 업데이트에 데여봤고, Bloc 패턴을 사용할만한 대규모 프로젝트를 진행하는 것이 아니기에, 당분간의 앱 개발에선 Riverpod을 사용할 것 같다.