Flutter Riverpod

Learn via video courses
Topics Covered

Overview

Riverpod is a state management library for Flutter that offers a simplified and declarative approach to managing and sharing data across an application. It extends the capabilities of the Provider package. With the Riverpod Flutter package, you can define providers that hold application states and consume them in different parts of your app. It introduces the concept of a Scoped Reader, which allows efficient access to the state without unnecessary rebuilds. Riverpod flutter also offers powerful dependency injection capabilities using providers, making it easier to manage complex dependency graphs.

Introduction

Riverpod is a powerful state management library for Flutter applications, designed to simplify and optimize state handling and dependency management in your applications. Riverpod Flutter simplifies the process of managing and sharing state/data across different parts of an app which comes in very handy when building large and complex data with data being thrown around across various pages or classes and instances. It is built on top of the popular 'Provider' package and offers additional features and flexibility.

Traditional approaches to state management, such as using InheritedWidgets or passing callbacks through widget constructors, can quickly become complex and difficult to maintain. Hence, Riverpod flutter addresses these challenges by introducing a more intuitive approach by leveraging the power of Dart's BuildContext and introducing the concept of Scoped Reader, which allows widgets to access the state without the need for explicit passing of dependencies.

What is Flutter Riverpod?

Flutter provider is a library that makes state management easier by introducing the concept of providers, which encapsulate the application state and make it accessible to different parts of the app. This eliminates the need for passing down state explicitly through constructors, leading to cleaner and more maintainable code.

The power of Riverpod lies in its ability to handle state updates efficiently, minimizing unnecessary rebuilds and optimizing performance.

At its core, Riverpod flutter promotes the idea of a single source of state for your app. Providers hold this state and allow widgets to read and update it as needed. Providers in Riverpod represent pieces of state in your application.

It uses the watch function on the ref object to watch the object and update it based on any changes or updates made.

Some key features to keep in mind about riverpod are:

  • Compile time safety
  • Reactive: Only rebuilds widgets that depend on changed providers
  • Easy testing
  • Handles asynchronous data fetching
  • Provides providers to encapsulate state outside the widget tree

Some common issues that riverpod flutter helps resolve are:

  • Mutation of state
  • Separation of concerns
  • Testability

Getting Started with Riverpod

To get started with riverpod flutter, you need to add the riverpod package as a dependency in your Flutter project.

Navigate to your project root directory open the pubspec.yaml file and add the following line under the dependencies section:

Once you have added the dependency, run the following command to fetch and install the package:

Now, you need to import the riverpod flutter package to the files where you need to use it.

Once you have the dependency installed and imported into the file you wanna use, start by creating a provider.

Provider

A provider is an object that encapsulates a piece of state and allows listening to that state.

Using a provider to encapsulate a piece of state offers several advantages:

  • Facilitates accessing the state from multiple locations effortlessly. Providers serve as a complete substitute for patterns like Singletons, Service Locators, Dependency Injection, or InheritedWidgets.
  • Simplifies the process of combining the state with other state objects. If you've ever faced challenges merging multiple objects into one, providers handle this scenario directly.
  • Enables performance optimizations, whether it's filtering widget rebuilds or caching expensive state computations. Providers ensure that only the components impacted by a state change are recomputed, enhancing efficiency.
  • Enhances the testability of your application. Providers eliminate the need for complex setUp/tearDown steps, and any provider can be overridden to behave differently during testing, allowing easy testing of specific behaviors.
  • Facilitates seamless integration with advanced features like logging or pull-to-refresh.

Creating a Basic Provider

Providers come in many variants, but they all fundamentally work the same way.

The most common usage is to declare them as global constants like so:

In the above example, we declare a global variable called myProvider which will be used in the future to read the state of our provider.

Riverpod flutter provides us with various types of Providers, In this case, we chose to use the most basic of all, Provider. Along with a function that creates the shared state.

That function will always receive an object called ref as a parameter. This object allows us to read other providers, perform some operations when the state of our provider will be destroyed, and much more.

Types of Providers

Each type of provider serves a specific purpose, catering to different use cases. Understanding the various types of providers is essential for effectively managing the state and services in your app.

Provider

The Provider is the most basic type of provider in the Riverpod library. It allows you to expose a value to the widget tree and automatically recomposes the widgets whenever the value changes. This is useful for providing immutable data or services that don't need to be mutated. To create a Provider, you can use the Provider constructor and pass the initial value or instance.

StateProvider

The StateProvider is an extension of the Provider that holds a mutable state. It exposes a way to modify the state. When the state changes, it automatically triggers a widget tree recomposition, updating the UI with the latest value. This is ideal for managing simple states like counters or toggle switches.

StateNotifierProvider

The StateNotifierProvider combines the concept of a StateNotifier with a Provider. It allows you to expose a mutable state that can be changed by calling methods on the StateNotifier. When the state changes, the widget tree is rebuilt accordingly.

FutureProvider

The FutureProvider is designed to handle asynchronous operations that return a Future of any type. It allows you to expose the result of the future to the widget tree, updating the UI when the future completes with the data or an error. This is a common provider used to return the result of an API call or so.

StreamProvider

The StreamProvider is similar to FutureProvider, but it deals with streams of any type instead of futures. It updates the widget tree whenever a new value is emitted from the stream. This is the kind of provider used to handle a stream of results from an API.

ChangeNotifierProvider

The ChangeNotifierProvider is the type of provider that is combined with ChangeNotifier to manipulate advanced states. It returns a subclass of ChangeNotifier. It is often implemented as a complex state object that requires mutability.

Reading Providers

In Riverpod, reading providers is a fundamental aspect of accessing and interacting with state or data exposed by providers within the widget tree. By reading providers, widgets can subscribe to changes in the state and automatically rebuild when the state changes, ensuring a reactive user interface.

The ref object is a crucial element that enables interaction with providers, whether from a widget or another provider. This object plays a central role in reading, writing, and observing state changes.

Obtaining a "ref" Object

To begin reading a provider, you need to obtain a "ref" object. Every provider receives a "ref" as a parameter during its definition. This "ref" object allows the provider to interact with other providers and manage the state within the widget tree.

Obtaining a "ref" from a Widget

Widgets themselves do not have a "ref" parameter. However, Riverpod flutter provides several ways to obtain a "ref" from widgets, allowing them to consume providers effectively.

The most common way to obtain a "ref" in the widget tree is by replacing StatelessWidget with ConsumerWidget. The ConsumerWidget is identical in use to StatelessWidget, with the only difference being the extra "ref" parameter in its build method.

Once we have obtained a "ref," we can use it to interact with providers in different ways:

  1. Using ref.watch to observe a provider:
    The ref.watch method is used inside the build method of a widget or inside the body of a provider to have the widget/provider listen to a provider.

  2. Using ref.listen to react to a provider change:
    ref.listen is similar to ref.watch, but instead of rebuilding the widget/provider if the listened-to provider changes, ref.listen calls a custom function.

    In the above example, the provider "anotherProvider" listens to the changes of "counterProvider". Whenever counterProvider changes, the custom function defined inside ref.listen is executed.

  3. Using ref.read to obtain the state of a provider:
    ref.read is used to obtain the state of a provider without listening to it. It is commonly used inside functions triggered by user interactions.

Modifiers

In Riverpod, Providers come with built-in modifier functionalities that enable developers to add extra features and behaviors to their providers.

Modifiers can be used on various types of providers and are declared using a syntax similar to named constructors.

.autoDispose Modifier

The .autoDispose modifier is used to automatically destroy the state of a provider when it is no longer being listened to.

This ensures that the state is efficiently managed and released from memory when it is no longer needed, preventing memory leaks and improving the overall performance of the application.

This modifier is particularly useful when dealing with providers that contain data with a limited lifecycle or that are frequently created and destroyed.

.family Modifier

The .family modifier allows developers to create a provider that depends on external parameters. It is a powerful feature that enables the creation of providers based on dynamic values, making providers more dynamic and versatile.

The primary purpose of the .family modifier is to create specialized providers that depend on additional parameters.

Using Multiple Modifiers

Riverpod Flutter allows providers to use multiple modifiers simultaneously. This means a provider can have multiple functionalities added to it, making it more versatile and adaptable to different scenarios.

Best Practises

  • Understand Provider Scopes to use effectively.
  • Keep Providers Simple and Single-Responsibility.
  • Use Provider Modifiers Wisely.
  • Prefer to Watch over Read.
  • Avoid Using ref.read Inside Build Methods.
  • Test Providers in Isolation.
  • Use const Widgets When Possible.
  • Use DevTools for Debugging.
  • Optimize the Use of Select().
  • Document and Comment Your Code.
  • Stay Updated with Riverpod as the library is constantly evolving.

Example App

We are going to build a simple Todo-list app where you can add a list of things or items, checklist items in the list if completed, delete items, and more.

For this application, we will be using the StateNotifierProvider.

To get started, After creating a new Flutter project, the first thing you need to do is navigate to the project root directory and add the riverpod library as a dependency in the pubspec.yaml file.

Then import the library in all the files you wanna use it in. In this case, it will only be the main.dart file.

The source code for the application will be something like this:

main.dart file:

In the above code:

  • Todo class:
    Represents a Todo item with properties id, description, and completed. It also provides a copyWith the method to create a new Todo with updated values.
  • TodosNotifier class:
    A StateNotifier subclass responsible for managing the list of Todos. It provides methods to add, remove, and toggle Todos.
  • todosProvider:
    A StateNotifierProvider that creates an instance of TodosNotifier to manage the state of the Todo list.
  • TodoListView class:
    A ConsumerWidget that displays the list of Todos using CheckboxListTile widgets. It listens to changes in the Todo list using the todosProvider.
  • AddTodoView class:
    Another ConsumerWidget that provides a UI to add new Todos. It includes a TextField for entering Todo descriptions and an ElevatedButton to add a new Todo.
  • main function:
    Initializes the app by running a ProviderScope and starting the MyApp widget.
  • MyApp class:
    The root widget of the app. It sets up the MaterialApp with a dark theme and defines the UI structure using a Scaffold with a TodoListView and an AddTodoView.

The output of the application will look something like this:

example app of using riverpod in flutter

You can find the complete source code of the project here: link

Conclusion

  • Riverpod is a powerful state management library for Flutter applications, providing a robust and flexible way to manage states across the widget tree.
  • With Riverpod, developers can easily define and consume providers to share and access data throughout the app, replacing patterns like Singletons, Service Locators, Dependency Injection, or InheritedWidgets.
  • The library introduces different types of providers, such as StateProvider, StateNotifierProvider, and FutureProvider, each serving specific use cases and catering to different state management scenarios.
  • Riverpod offers various built-in modifiers like .autoDispose and .family, allowing developers to enhance providers with extra functionalities and create unique providers based on external parameters.
  • When reading providers, developers can use ref.watch, ref.listen, and ref.read to obtain and interact with the state of a provider, depending on their specific use cases and requirements.