How to Periodically Refresh API using Riverpod
Have you ever wondered what the easiest way to periodically refresh API calls every few seconds in Flutter? You can achieve this with just two lines of code in Riverpod. Learn how.
Old School FutureBuilder for API calls
Traditionally, we use a FutureBuilder in Flutter to handle API calls :
There is nothing new here. FutureBuilder takes a Future as an API call and emits its state in hasData and hasError states. This works fine for one-time API calls, but if you want to periodically refresh it, then you need to have some additional logic inside our ProductListPage.
We usually do this by having a periodic timer like Time.periodic or Stream.periodic on initState() to update the widget's state at defined intervals using setState({}). To make this work, we need to have a StatefulWidget, and a ValueKey in FutureBuilder and also clean up the timer on dispose().
While effective, the FutureBuilder approach can become difficult when you need to implement features like this. In such cases, Riverpod offers a more centralized and efficient way to manage this.
To change this, our first step is to….
Replace FutureBuilder with FutureProvider
Riverpod introduces the FutureProvider to easily manage API calls and their associated states. So we are going to replace FutureBuilder with FutureProvider and use it inside ProudcListPage which extends ConsumerWidget:
Note: If you are already familiar with Riverpod API then you can skip the below explanation section. Checkout this video for Riverpod walkthrough.
Explanation:
FutureProvider: With Riverpod's FutureProvider, we’re fetching product data. Just plug in an async function to handle the API call and get back your list of products.
ref.watch(productListProvider): The ConsumerWidget subscribes to the productListProvider. The ref object provides access to Riverpod's state management system. Calling ref.watch on a provider allows the widget to rebuild whenever the provider's value changes.
AsyncValue.when: This method provided by Riverpod helps handle the different states of the asynchronous operation (loading, error, data). It takes three functions as arguments, each handling a specific state:
loading: This function is called while the data is being fetched. Here, it displays a CircularProgressIndicator.
error: This function is called if an error occurs. Here, it displays an error message.
data: This function is called when the data is successfully fetched. Here, it builds a ListView.builder to display the product data.
Now this works as a one-time API call. How to periodically refresh this API? It’s simple. Use Timer.
Timer Inside FutureProvider
To achieve periodic refreshes, add a Timer function directly within our FutureProvider:
And that’s it! Our product list API will now refresh every 5 seconds. You can run the app and test it yourself. Now, how does it work?
If you have noticed, we're NOT using Timer.periodic here; instead, we are just using a one-time Timer. Here's how it works line-by-line:
The code execution starts from Line 3. It sets a Timer for 5 seconds, which will have a callback with the code ref.invalidateSelf(). This code will NOT execute immediately since it will execute after 5 seconds is passed.
So now our execution goes from Line 3 directly to Line 6.
Line 6 waits for the getProducts() API to complete and return the result. Simultaneously, the Timer function is also running. Since our API call succeeds first, it displays the data on the UI.
After 5 seconds, Line 4 will be executed, ref.invalidateSelf(), which invalidates the FutureProvider, preserving the old value. Invalidating starts the process again from Line 3, and the same process starts again and keeps repeating.
With this approach, the provider automatically triggers a refresh every 5 seconds, keeping your UI up-to-date with the latest data from the API.
What If API Fails
Since the Timer does not depend on Future states, it will keep running. For example, if the API fails, it will call the error: (s, e) => { } callback on products.when() on ProductListPage and will retry the API call again after 5 seconds.
Timer Inside Class-based Notifier
The same periodic refresh pattern applies to custom class-based Notifier providers in Riverpod by adding the Timer inside the build() :
Use Extension to Avoid Duplication
If you need periodic refresh functionality in multiple providers, create a reusable extension:
With this extension, we can use this extension where ref is available:
This approach reduces code repetition and improves maintainability, especially when periodic refreshes are required in multiple parts of your application.
Conclusion
Riverpod simplifies periodic API refreshes in Flutter apps. Here's why this approach is advantageous:
Cleaner Code: Riverpod providers promote separation of concerns, making your code easier to read and understand.
State Management: Riverpod elegantly handles the different states associated with periodic refresh (loading, data, error).
Easy to Test: You can Mock/fake providers to test your widget for UI and unit test provider for timer logic.
No Manual Cleanup: Riverpod providers dispose widgets appropriately, so you don't have to worry about manually clearing timers or streams on dispose().
That’s it, folks!!
Check out if you are interested in the video version of this blog post, which I have published on YouTube.
If you have any questions, please let me know in the comments, and don't forget to subscribe to stay tuned for upcoming tips and tricks in Flutter.
You can also follow me on Twitter for any updates.
What if I would like to stop this timer after some duration?