At Bam, we've been developing applications for a little while now. But the Flutter ecosystem is moving at a fast pace, especially in State Management (which was and still is a hot topic in the Flutter community !). For the new flagship application of one of our clients, we needed to choose the best library for our use case and we ended up choosing Riverpod, one of the newcomers. Developed by the well known Flutter developer Remi Rousselet, it seems to be the best choice.
In this article I'm going to explain what are the differences between Provider (our default choice for some time now) and Riverpod, and what led us to choose Riverpod.
Provider is a well known solution for state management. At its base, the InheritedWidget, a common widget included in the Flutter Framework that provides the ability to pass information down the widget tree without passing props.
Even if it's widely used, it comes with several drawbacks.
In order to get an instance of an object provided with Provider, you need to use its type. It means that you can only have one provider of one given type at the same time in your widget tree. While it can be limiting, it also forces you to wrap some simple values into more complex classes, helping sometimes with business naming...
Combining different providers can be really verbose especially if the number of dependencies grows. It also means that you should be confident in the fact that the dependencies have been properly initialized. Changing the order of your providers, or trying to use one provider elsewhere can be troublesome. Which leads us to the third drawback.
By using Provider, you can have no assurances that your code will properly work. If you move a Widget, you might also be moving a Provider and some dependencies in your widget tree will not properly receive its dependencies. It makes taking the codebase in hand harder than it should be for large scale projects.
For all these reasons, we wanted to consider another solution for our next project.
Riverpod was first published in May 2020, and is currently (as of October 2021) undergoing the final refactorisation to go into the 1.0 version. Yet, despite its relative young existence, we decided to give it a try for our next project. It's promises seem to be way too compellent to miss.
Firstly, Riverpod doesn't depend on the context to share information down the widget tree. It makes it easy to instantiate Providers (Riverpod's Provider) as global variables that don't depend on Flutter. It means that if a Provider is called somewhere, the path to properly initialise it will always be available.
Since providers are just variables, you can import them easily from anywhere and they can return the same type. It makes separating business logic in different file ways much easier. You can create a provider that will return a boolean value without needing to encapsulate the data in an object with a specific type.
Riverpod is filled with a lot of dev helping features making the developer much faster and less error prone. The ability to group the providers with `.family` is something that makes a lot of sense during the development and helps keep each provider having a single responsibility. The `.autodispose` helper is something that was really helpful in order to ensure that all the state was properly disposed of after we entered something in a form. Finally, the ability to map a FutureProvider to data, loading and error is something that helps make sure that all the cases are properly handled.
Our experience with Riverpod has been mostly great. It allowed us to spend more time focusing on writing business logic than to make sure that all cases were properly handled. There were still some limitations that I will describe here, but keep in mind that we are mostly happy with the solution!
The onboarding process however seems to be a little bit harder as it introduces quite a lot of new Widgets and newcomers are unsure when to use a ConsumerWidget, a Consumer Widget or just a context.wach. We feel that it is something that is being addressed in the 1.0.0 version of Riverpod and we can't wait for it to become stable so we can move to the new semantic.
It also sometimes gives a false sense of security. In the first iteration of the project, the state was really simple and it was easy to handle all the cases with maybeMap. But as the project grows, using maybeMap leads to more errors since the linter wasn't helping us to detect when some cases were not properly handled. It is the same thing that could happen if you put a default to switch on an Enum and do not check them properly.
When we started the project, we decided to stick with the `0.14` version of Riverpod. The version 1.0.0 is introducing a lot of breaking (as expected). If you are going to create a huge code base in Flutter, maybe consider something that will be a little more stable for the time being.
Something that we didn't expect before choosing Riverpod is the fact that the hot reload will be broken in the Providers. Since Riverpod does not depend on the context, the usual Hot Reload mechanism is not working in Riverpod. Modifying the content of a Provider will require your app to be restarted in order to get the new content of the provider. This is something that can slow your development process quite a bit and that we didn't fully anticipate.
Remi Rousselet seems to be working on the subject, but not sure if this will be solved.
Even though the library is still young, there are a lot of positive things in it and it allows us to speed up a lot of our development process and error handling.
The hot reload issue is still something that worries me and I hope to see it fixed with a workaround (that hopefully will not include code generation ?)