Journey to the riverpod for my flutter app.
Tags: flutter, architecture, riverpod, dart
Published: 2022-10-31 01:59:38

---
title: Journey to the riverpod for my flutter app.
published: true
description: 
tags: #flutter #architecture #riverpod #dart
---

In this article, I'd like to talk about how I ended up using Riverpod as a state management system for my flutter app.

1. Provider with MVVM
2. `get_it` for global dependencies
3. Riverpod

## Provider with MVVM
When I started the flutter project as a newbie two years ago, I wanted to build something similar to the MVVM pattern I used to use for Android application development. After some research, I found [Provider](https://pub.dev/packages/provider) the most straightforward option. (I wasn't even thinking of state management at that time)

So, I was doing something like this at the entry point of the app:
```dart
final serviceA = ServiceA();
final serviceB = ServiceB();
MultiProvider(
  providers: [
    Provider<RepoA>(create: (_) => RepoA(service:serviceA)),
    Provider<RepoB>(create: (_) => RepoB(service:serviceA)),
    Provider<RepoC>(create: (_) => RepoC(service:serviceB)),
  ],
  child: MaterialApp(),
)
```
I've been initializing the `services` and `repositories` at the application launch time and adding them into the Provider using the `MultiProvider`. It allowed the `ViewModels` of each page to access the repositories. (Yes, one `view`, one `ViewModel`)

```dart
class ViewModelA with ChangeNotifier {
  ViewModelA(this.repo) {
    init();
  }
  final RepoA repo;
  String title;

  Future<void> init() async {
    title = "test";
    notifiyListeners();
  }
}

// Wrap the page with ChangeNotifierProvider
class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => new ViewModelA(Provider.of<RepoA>(context, listen: false)),
      child: PageView(),
  );
}
```

I could access the' ViewModel' in any widgets used in the `PageView` using the `Selector`.
```dart
Selector<ViewModelA, String>(
  selector: (_, vm) => vm.title,
  builder: (_, data, __) {
    return Text('title: ${data}');
  }
)
```
It was annoying to write the boilerplate codes of the `Selector` every time I needed to use variables in the ViewModel, but I helped to reduce unnecessary rendering. So far, It's not bad.

After growing the code bases with many repositories and services, I realized that every time I add more repositories in the global provider scope with the `MultiProvider`, it creates the cascading tree in the widget hierarchy. It made debugging super tricky with the Widget Inspector tool.

One day, I decided to remove `MultiProvider` and convert all global dependencies as singletons. Technically they were already singletons but just living in the Provider scope anyway.

## get_it for global dependencies
After some research, I found the [get_it](https://pub.dev/packages/get_it) looked promising.
I replaced `MultiProvider` with getIt singletons at the application start:
```dart
final serviceA = ServiceA();
final serviceB = ServiceB();
getIt.registerSingleton<RepoA>(RepoA(service:serviceA));
getIt.registerSingleton<RepoB>(RepoB(service:serviceA));
getIt.registerSingleton<RepoC>(RepoC(service:serviceB));
```

And the View and ViewModel like this:
```dart
class ViewModelA with ChangeNotifier {
  // This allows passing mocked repository to the ViewModel for testing purposes
  ViewModelA(RepoA? repo): _repo = repo ?? GetIt.get<RepoA>() {
    init();
  }
  final RepoA _repo;
  String title;

  Future<void> init() async {
    title = "test";
    notifiyListeners();
  }
}

class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => new ViewModelA(),
      child: PageView(),
  );
}
```

Now, I don't see the cascading tree in the widget inspector, and much more comfortable debugging the widgets. I also liked it because the widget doesn't need to know what dependencies the ViewModel needs. Thanks to the GetIt can give it to me without `context`. But I still have a slow application start problem.

A few months later, I wanted to tackle the start-up time issue and realized I might have a few options.
1. Use lazy singleton with GetIt.
2. Go for another approach.

I could have converted the global dependencies to lazy singleton with GetIt so that it doesn't need to initialize all dependencies at the application start. However, It would still keep in the memory until the app completely shut down. I wanted something that could automatically initiate and de-initiate based on the need.
So another round of research started!

## Riverpod

In the meantime, I also was working on a react project that uses the [React Query](https://tanstack.com/query), and even though it's not a client-side state management library, I found it a simple and nice way of managing states. I wanted to have something similar but for flutter. Finally, the [Riverpod](https://riverpod.dev/) caught my eye. The developer of the Provider developed Riverpod as a successor of the Provider. (Later, I found [Recoil](https://recoiljs.org/) is the one I've been looking for in the React app for client state management, and it has a lot of similarities with Riverpod)

As a first step, I converted all singleton services and repositories into Riverpod providers. I also defined `abstract classes` for better testability.

```dart
abstract class ServiceA {}
class ServiceAImpl implements ServiceA {}
abstract class ServiceB {}
class ServiceBImpl implements ServiceB {}

abstract class RepoA {
  Future<List<Entity>> getMany();
}
class RepoAImpl implements RepoA {
  RepoAImpl(this.service)
  final ServiceA service;

  @override
  Future<List<Entity>> getMany() {
    return service.getMany();
  };
}
abstract class RepoB {}
class RepoBImpl implements RepoB {
  RepoBImpl(this.service)
  final ServiceA service;
}
abstract class RepoC {}
class RepoCImpl implements RepoC {
  RepoCImpl(this.service)
  final ServiceB service;
}

final serviceAProvider = Provider<ServiceA>((_) => ServiceAImpl());
final serviceBProvider = Provider<ServiceB>((_) => ServiceBImpl());

final repoAProvider = Provider<RepoA>((ref) => RepoAImpl(ref.watch(serviceAProvider)));
final repoBProvider = Provider<RepoB>((ref) => RepoBImpl(ref.watch(serviceAProvider)));
final repoCProvider = Provider<RepoC>((ref) => RepoCImpl(ref.watch(serviceBProvider)));
```

I also started getting rid of the ViewModels. Since I can now define a state as a provider that is accessible from any widgets, the ViewModel was too heavy as a state. This way, I was able to write more reusable codes.

**Create state providers**
```dart
// Simple future state provider
final stateAProvider = FutureProvider<List<EntityA>>((ref) {
  return ref.watch(repoAProvider).getMany();
});

// State provider with notifier that contains methods.
final stateBProvider = StateNotifierProvider<EntityB>((ref) {
  return StateBNotifier(EntityB(), ref.watch(repoAProvider));
});

class StateBNotifier extends StateNotifier<ThemeMode> {
  StateBNotifier(super.state, this.repo);
  final RepoA repo;

  void fetch() {
    state = repo.getMany();
  }
}
```

I can access the providers from widgets.
```dart
class WidgetA extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final stateB = ref.watch(stateBProvider);
    return InkWell(
      onTap: () => ref.read(stateBProvider.notifier).fetch(),
      child: Text('${stateB.name}'),
    );
}
```

With the Riverpod, I could make the state lighter and more reusable. I often create a container widget that holds the state that wraps the pure widgets. This way, the design widget is reusable as a design widget without business context. Providers and widgets are all unit-testable by simply mocking the repository or other dependency of the Provider.