Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Jhin Lee
Jhin Lee

Posted on

     

Journey to the riverpod for my flutter app.

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 foundProvider 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:

finalserviceA=ServiceA();finalserviceB=ServiceB();MultiProvider(providers:[Provider<RepoA>(create:(_)=>RepoA(service:serviceA)),Provider<RepoB>(create:(_)=>RepoB(service:serviceA)),Provider<RepoC>(create:(_)=>RepoC(service:serviceB)),],child:MaterialApp(),)
Enter fullscreen modeExit fullscreen mode

I've been initializing theservices andrepositories at the application launch time and adding them into the Provider using theMultiProvider. It allowed theViewModels of each page to access the repositories. (Yes, oneview, oneViewModel)

classViewModelAwithChangeNotifier{ViewModelA(this.repo){init();}finalRepoArepo;Stringtitle;Future<void>init()async{title="test";notifiyListeners();}}// Wrap the page with ChangeNotifierProviderclassPageAextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnChangeNotifierProvider(create:(_)=>newViewModelA(Provider.of<RepoA>(context,listen:false)),child:PageView(),);}
Enter fullscreen modeExit fullscreen mode

I could access the' ViewModel' in any widgets used in thePageView using theSelector.

Selector<ViewModelA,String>(selector:(_,vm)=>vm.title,builder:(_,data,__){returnText('title:${data}');})
Enter fullscreen modeExit fullscreen mode

It was annoying to write the boilerplate codes of theSelector 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 theMultiProvider, it creates the cascading tree in the widget hierarchy. It made debugging super tricky with the Widget Inspector tool.

One day, I decided to removeMultiProvider 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 theget_it looked promising.
I replacedMultiProvider with getIt singletons at the application start:

finalserviceA=ServiceA();finalserviceB=ServiceB();getIt.registerSingleton<RepoA>(RepoA(service:serviceA));getIt.registerSingleton<RepoB>(RepoB(service:serviceA));getIt.registerSingleton<RepoC>(RepoC(service:serviceB));
Enter fullscreen modeExit fullscreen mode

And the View and ViewModel like this:

classViewModelAwithChangeNotifier{// This allows passing mocked repository to the ViewModel for testing purposesViewModelA(RepoA?repo):_repo=repo??GetIt.get<RepoA>(){init();}finalRepoA_repo;Stringtitle;Future<void>init()async{title="test";notifiyListeners();}}classPageAextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnChangeNotifierProvider(create:(_)=>newViewModelA(),child:PageView(),);}
Enter fullscreen modeExit fullscreen mode

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 withoutcontext. 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 theReact 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, theRiverpod caught my eye. The developer of the Provider developed Riverpod as a successor of the Provider. (Later, I foundRecoil 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 definedabstract classes for better testability.

abstractclassServiceA{}classServiceAImplimplementsServiceA{}abstractclassServiceB{}classServiceBImplimplementsServiceB{}abstractclassRepoA{Future<List<Entity>>getMany();}classRepoAImplimplementsRepoA{RepoAImpl(this.service)finalServiceAservice;@overrideFuture<List<Entity>>getMany(){returnservice.getMany();};}abstractclassRepoB{}classRepoBImplimplementsRepoB{RepoBImpl(this.service)finalServiceAservice;}abstractclassRepoC{}classRepoCImplimplementsRepoC{RepoCImpl(this.service)finalServiceBservice;}finalserviceAProvider=Provider<ServiceA>((_)=>ServiceAImpl());finalserviceBProvider=Provider<ServiceB>((_)=>ServiceBImpl());finalrepoAProvider=Provider<RepoA>((ref)=>RepoAImpl(ref.watch(serviceAProvider)));finalrepoBProvider=Provider<RepoB>((ref)=>RepoBImpl(ref.watch(serviceAProvider)));finalrepoCProvider=Provider<RepoC>((ref)=>RepoCImpl(ref.watch(serviceBProvider)));
Enter fullscreen modeExit fullscreen mode

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

// Simple future state providerfinalstateAProvider=FutureProvider<List<EntityA>>((ref){returnref.watch(repoAProvider).getMany();});// State provider with notifier that contains methods.finalstateBProvider=StateNotifierProvider<EntityB>((ref){returnStateBNotifier(EntityB(),ref.watch(repoAProvider));});classStateBNotifierextendsStateNotifier<ThemeMode>{StateBNotifier(super.state,this.repo);finalRepoArepo;voidfetch(){state=repo.getMany();}}
Enter fullscreen modeExit fullscreen mode

I can access the providers from widgets.

classWidgetAextendsConsumerWidget{@overrideWidgetbuild(BuildContextcontext,WidgetRefref){finalstateB=ref.watch(stateBProvider);returnInkWell(onTap:()=>ref.read(stateBProvider.notifier).fetch(),child:Text('${stateB.name}'),);}
Enter fullscreen modeExit fullscreen mode

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.

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
dvaefurn profile image
Davefurn
  • Work
    Freelance Front-End Developer
  • Joined

Very insightful!!!! Helped me a lot

CollapseExpand
 
leehack profile image
Jhin Lee
Full stack developer | GDE Flutter | Flutter & GDG Montreal Organizer | Scrum Master
  • Location
    Montreal
  • Work
    Full stack developer @Unity
  • Joined

Thanks! Glad to hear that! ;)

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Full stack developer | GDE Flutter | Flutter & GDG Montreal Organizer | Scrum Master
  • Location
    Montreal
  • Work
    Full stack developer @Unity
  • Joined

More fromJhin Lee

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp