Fluttering through Riverpod — State Management
I am trying out Riverpod in a Pet project and here I share how I have implemented state management with it.
State Management in a Flutter app
I will not try to describe state management in this article, as it is very successfully covered on official docs. What I am going to show is how I implemented it in a Pet project with the help of Riverpod.
So for more information on State Management, please read the official Flutter Docs.
I will keep a short description, taken from official docs, that briefly describes it:
So a more useful definition of state is “whatever data you need in order to rebuild your UI at any moment in time”
Riverpod
As per the official page describes, Riverpod is “A Reactive Caching and Data-binding Framework”. One option of many state management in the Flutter world, you can check the highlighted options here https://docs.flutter.dev/data-and-backend/state-mgmt/options
Note on Riverpod version and this Project
I maintain this project on my free time, nights and weekends. Will do my best to keep it up to date with latest Riverpod changes. So far I have worked on the development with official exemples as guidance on https://riverpod.dev/docs (July/2023) which uses StateNotifier. The version of flutter_riverpod used at the moment I created this article and also the pet project is 2.3.6.
Installing Riverpod
You can find the latest version of the package in pub.dev page, also the instructions to install it plus examples. But basically you add it through the usage of this command:flutter pub add riverpod
Which will then add the package to your pubspec.yaml file, under dependencies configuration.
The Pet project
My pet project consists of a Parking App, inspired by a parking app that we have available here in Germany. But not intended to be commercially used, it will be my playground for Flutter topics.
Project Architecture
The project structure is simple and testable. Was done thinking about Clean Architecture concepts, specially the separation of concerns. Having the business logic layer independent of frameworks or other external factors.
Project setup has implementations separated per feature. So in each feature folder you can observe the following structure:
Let’s take a feature to use as an example and explore more how it was implemented. Feature taken will be places, and you can find its code here.
- Data
Inside data layer we have model, that will have the class ParkingPlace that represents what comes from the Places REST API. As we don’t need all the data that comes from the API we map only what we need in order to build the list of places. PlacesRepository is an abstraction of what we need to fetch the places from the API. PlacesRepositoryImpl has the actual implementation that fetches the data. So let’s say we in the future need to change the data source for any reason, we only have to change the actual implementation.
- Domain
In the Domain layer, we could potentially have also models inside. In the pet project the models I have inside the data layer are already enough to handle the data. I guess this is a decision that people can make accordingly with the project and businesses needs. Here I have one example where I created a model inside domain and the proper mapper to map the data from data layer to domain layer.
In this layer resides also the Interactor abstraction and implementation. Interactor can also be found in different implementations with different naming convention, as Use Case. In this layer is where the business logic is concentrated, without dependency of any external player.
- Presentation
In the Presentation layer resides all that concerns UI functioning. In it of course we have our widgets / screens / components, but also the states and the notifiers implementation with Riverpod.
- Presentation — States
The definition of states helps us to literally describe how we want the UI to behave given some action or input. In the example a list of places should be displayed in the map when the screen is presented and then user would be able to touch on a marker and select a place to park.
A list would potentially have a loading indicator, the markers being added in the map and in case something fails a snackbar could be shown or something else that indicates the failures.
- Presentation — Notifiers
Notifier for me serves as a Presenter. Given some action, it manages the states that the UI will render on component / widget level.
The searchPlaces, for example. When it starts we want the UI to display a loading while the search is in progress, then once the result is complete we want the screen to display either the result or failure indication.
- Presentation — Widgets
In order for us to get the instance of the Notifier in the Widget implementation, instead of extending StatefulWidget, we need to extend ConsumerStatefulWidget from Riverpod dependency. Also instead of extending State<> in the state class that we create, would have to extend ConsumerState<>. With that we gain access to ref which we will use to get the instance of the notifier we created before. Through ref.read() to read the instance of our notifier.
Inside _getCurrentLocation method, once the current location is known the searchPlaces from the notifier is called.
And the state changes are observed as following, using ref.watch() watching for the placesNotifierProvider inside the build method. So every time the state changes, the build method is called and we can update UI contents.
Creating the dependencies and Places notifier provider
If you notice in all the code snapshots above, the dependencies are received through constructor parameter. This is inversion of control, where the dependencies instances are not created inside the class implementation but received through parameters that are referenced by its abstraction instead of concrete classes. This gives us flexibility to change specific implementation without changing whoever depends on it. Also helps testability.
In this file called places_provider, I’ve placed all the instantiation needed for all the dependency setup to work. It provides the http.Client, Repository, Interactor and notifier instances.
Wrap your Application in a ProviderScope
One important thing to mention is that you have to wrap your application in a ProviderScope in order for the widgets to be able to access the providers.
More on this project…
As soon as I explore more topics, will be posting updates about them. So far, here is the repository, feel free to have a look in the project and if that helps somehow I am glad to have shared it :)
Thank you for reading!