#1 Overview
This post is about using Riverpod for state management in a simple web application created using Flutter. The application has a Page ‘ARTICLES’ that renders a grid of tiles. Each tile corresponds to an article. Each article has metadata such as Title, Description, tags, like/dislike counts. This metadata is provided by a remote API. Whenever the like/dislike button is pressed, the count is updated at the remote database and latest counts will be fetched (Note that, the articles can be liked / disliked by other users at the same time).
In terms of UI, these are the requirements to be met:
- When the Articles page is opened, fetch the counts from remote API and use them while rendering the tiles.
- When a like/dislike button is pressed, rebuild only the article tile that was clicked on. Do not rebuild all the tiles.
#2 Data model
The Data model of each article is shown below.
#3 Service — To fetch metadata from remote API, Update counts, and convert from JSON to Classes
#4 State management
Here, the state being managed is a List of ArticleMetadata. The state is changed in two cases:
- To fetch all articles, and up to date counts from the api
- Update the counts for a single article at the remote api, and then fetch the latest counts from it. When it is done, existing State is updated to reflect the latest data.
Note that, the same article might be liked / disliked by multiple users at the same time. So, we should always get the counts from the API. Otherwise, local state does not reflect up-to-date counts.
#5 Which provider should be used?
Using StateNotifierProvider seems to be the right fit for the job in this case.
#6 UI Changes
Requirement #1
go_router
is used for navigation.
Which widget to use to get the latest counts whenever articles page is opened?
- Initially, I was planning to use StatelessWidget. I thought of using
Consumer
in thebuild()
method. So that, I will have to access the provider viaref
and to the state. I could get the notifier usingref.watch(articlesProvider.notifier).getArticles()
, so that I can invoke the API. But, the problem is build() is invoked a number of times during UI rendering. Invoking the API in build() is not recommended. - My next option was using a StatefulWidget. Again, the API should not be invoked in the
build()
. But, State object has other methodsinit()
,didChangeDependencies()
. so I thought this should work. But, next challenge was How could I watch the provider, so that when the state changes widget can be rebuilt? In other words, How could I haveref
inside a stateful widget? - After going through the documentation, I read that ConsumerStatefulWidget and ConsumerState are the equivalent of a StatefulWidget with its State, with the difference that the state as a “ref” object. This time, the “ref” isn’t passed as parameter of the build method, but is rather a property of the ConsumerState object.
- The API call
ref.watch(articlesProvider.notifier).getArticles();
is placed in thedidChangeDependencies()
method. It is invoked when theArticlesGrid
is built, and it invokes the API, fetches the latest counts, and updates the local state. - In the
build()
method, the state is accessed usingList<ArticleMetadata> articles = ref.watch(articlesProvider);
inside aConsumer
widget'sbuilder()
.
Requirement #2
When like/dislike button is clicked on any article, only that tile should be updated, entire ListView should not be rebuilt. This is achieved using another provider currentArticle
and overriding its value with the actual Article
when the ListView
is built. It is based on the pattern used in this example - todo.