State Management using Riverpod in Flutter

State Management using Riverpod in Flutter

Riverpod is a Flutter Favourite State Management Library, which is an upgraded version of the Provider package. It fixes the issues persisting in the Provider package and also improves the time and space complexity. In this post, we will discuss what is the use of Riverpod in Flutter and how to use it in our applications.

Pre-requisitives 📦

The ValueNotifier and ChangeNotifier are required as they are used in combination with RiverPod. Firstly we will briefly discuss the Provider and its drawbacks so that we can understand the need for Riverpod.

Basics for Provider 🧢

When we want to share data across multiple pages/widgets we use State Management Techniques. One of the basic solutions to this is Dependency Injection. This means that we simply inject the dependencies into the widget tree via Constructor so that the descendants can directly access it. Issues with this approach are :

  • As the app grows so does the number of parameters in the constructor.
  • There is no way to get updated in the Widget Tree if data changes.

A Native Solution to this is Inherited Widget. We simply inject the dependency and we can access it in the widget tree using the BuildContext. One issue that we face with Inherited Widget is that too much boilerplate code is involved while implementing an Inherited Widget. To solve this issue Provider was introduced by Rémi Rousselet (the author of Provider).

Provider is InheritedWidget but simplified.

Using Provider you can simply create your Data Providers and Consume them down the Widget Tree. To create Providers which update on some events, ChangeNotifier and ValueNotifier are used in combination with Provider. These are the three methods using which we can use Provider:

  • Simple Non-Reactive Provider – Provider<T>
  • Reactive ChangeNotifier Provider – ChangeNotifierProvider<T>
  • Reactive ValueNotifier Provider – ValueNotifierProvider<T>

Issues with Provider 🔽

The provider is more than enough for small to medium size projects. However, we face some issues while using the provider:

  1. Results in Runtime Error, in case of the wrong implementation.
  2. Code gets coupled as the widgets are used to provide dependencies.
  3. Can not inject two dependencies of the same type.
  4. Requires BuildContext to use dependencies.

Introduction to Riverpod in Flutter 🎏

To fix all the issues persisting in Provider, the author of provider himself introduced Riverpod. Which improves the efficiency of listening and removing listeners as well. It can be implemented with ChangeNotifier and ValueNotifier. On top of the Provider package, it provides integration with StateNotifier as well. In Riverpod:

  • We can identify errors at compile time.
  • Now multiple dependencies of the same type can be injected.
  • No BuildContext required thus can be used in pure Dart projects as well.

StateNotifier is similar to ValueNotifier, however unlike ValueNotifier no one can modify the current State of the StateNotifier from outside the class.

To add Riverpod to your project, simply add the dependency in pubspec.yaml:

dependencies:
  flutter_riverpod: ^1.0.3

In RiverPod we declare all our Providers in the global scope(will discuss ahead). Thus RiverPod automatically searches for all Providers and injects them into the Widget Tree efficiently. To Enable Riverpod dependencies throughout the project we need to cover our application within the ProviderScope like this:

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
}

Now the ProviderScope will store states for all the Providers we declare. Before creating our first Provider, let’s look at some common terminology.

Basic Terminology for Riverpod in Flutter 🔡

Note that, Riverpod is an extension of Provider. Thus, the naming convention of both is similar.

  • Consumer: Consumer is simply a Widget that subscribes to a Provider. As the name suggests it consumes the events posted by any Provider.
  • Provider: Providers are the most important components of Riverpod. In short, you can think of providers as an access point to a shared state. Similar to the Providers we create when we use the provider package.
  • WidgetRef: WidgetRef is the object that is provided to us by Riverpod and can be used to interact with the Providers. We will be using WidgetRef to perform all the actions(modify, listen) on our provider.

Creating Providers in Riverpod

For the sake of simplicity, I will create a Provider which contains a single read-only value. We declare all the Providers in the global scope so that any widget in the widget tree can access any of the providers.

// Simple Provider exposes a read-only value
final textProvider = Provider((ref) => 'Hello World');

To consume this Provider in our code, we can use Consumer Widget or extend our class ConsumerWidget instead of Stateless Widget.

class MyApp extends ConsumerWidget {
 @override
 Widget build(BuildContext context, WidgetRef ref) {...}
}

// OR

class MyApp extends StatefulWidget {
  @override
  Widget build(BuildContext context){
   return Consumer(
      builder: (_, WidgetRef ref, __) {...}
    );
  }
}

In our example, we will extend the ConsumerWidget for sake of simplicity:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1

final textProvider = Provider((ref) => 'Hello World');

void main() {
  runApp(
    const MaterialApp(
      home: ProviderScope(
        child: TextPage(),
    ),
  ),
);
}

// 2 
class TextPage extends ConsumerWidget {
  const TextPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      body: Center(
        child: Text(
             // 3
             ref.watch(textProvider),
         ),
      ),
    );
  }
}

Code Explanation:

  1. We create a simple read-only Provider with the text “Hello World”.
  2. Instead of extending StatelessWidget, we extend the ConsumerWidget. We get an extra parameter of type WidgetRef using which we can interact with all our providers declared.
  3. Using the ref parameter we can interact with the provider. We use the watch() function to watch the current value of the provider.

We have now created our first Provider. However, this Provider is read-only and its value is never updated. Let’s implement the counter app next and understand how to create modifiable Providers.

Counter Application using Riverpod 🔢

There are 3 ways to implement the counter application. As we discussed we need a modifiable provider, to do so we can use one of these three:

  1. ChangeNotifier
  2. ValueNotifier
  3. StateNotifier

For the sake of simplicity, we will use the ChangeNotifier in our example as all must be familiar with it. If you don’t know regarding you can check my post on ChangeNotifier in Flutter. The components we will require are Data Model, Provider, and UI.

So firstly let’s create our data class that extends ChangeNotifier:

import 'package:flutter/foundation.dart';

class CounterNotifier extends ChangeNotifier {
  int value = 0;

  void increment() {
    value++;
    notifyListeners();
  }
}

Now let’s declare our provider which will be a ChangeNotifierProvider as we are using ChangeNotifier in our Data Class:

final counterProvider = ChangeNotifierProvider((ref) => CounterNotifier());

Finally, let’s implement the UI, we are here using ConsumerWidget instead of StatelessWidget:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_example/data/change_counter.dart';

// 1
final counterProvider = ChangeNotifierProvider((ref) => CounterNotifier());

// 2
class ChangeCounterPage extends ConsumerWidget {
  const ChangeCounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 3
          ref.read(counterProvider).increment();
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        // 4
        child: Text(ref.watch(counterProvider).value.toString()),
      ),
    );
  }
}

Code Explanation:

  1. Declare your ChangeNotifierProvider and store it in a variable.
  2. Instead of StatelessWidget extend ConsumerWidget so that we get the WidgetRef instance that we can use to access any provider available in the application.
  3. We use the ref.read() to access the singleton instance of the Data Class and perform operations available on it. In our case, the only operation available in our Data Class is the increment() operation.
  4. We use the ref.watch() to get the value of the provider and also to subscribe to future events. So that our widget rebuilds whenever the value changes.

Conclusion 🦾

In this post, we first discussed State in Flutter and looked at Riverpod as a State Management Solution. We learned how to create our providers and consume them. For this post we simply used the ChangeNotifierProvider, we will extend this post with a series of posts on the following as well:

Next, you may check out these Flutter essential topics which I covered in detail:

If you have any queries you can comment below and I will be happy to help you. If you are new to Flutter check my post on how to install Flutter on windows?

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
State Management in Flutter

What is State Management in Flutter

Next Post
Featured Image for StateNotifier in Flutter

Complete Guide to StateNotifier in Flutter

Related Posts