Flutter - Introduction to State Management Using Riverpod - GeeksforGeeks

07 Sep, 2021
Riverpod is a Reactive State-Management and Dependency Injection framework, it uses different providers to let us access and listen to state changes across our app, it is built by Remi Rousselet. If you don’t know what is state then I’ll recommend you to read this article first as it will be a little hard for you to understand Riverpod without understanding the “state” itself.
Riverpod is a state management helper. It basically makes our state (in clear words, our variables’ values) accessible in all parts of the app, it puts our state at the top of the widget tree and lets us listen to those state changes and update our User Interface accordingly. 
There are many types of providers which Riverpod offers, We’ll go over them one by one.
Providers are the most important part of a Riverpod application, “A provider is an object that encapsulates a piece of state and allows listening to that state.”
Types of Providers:
StateProvider
FutureProvider
StreamProvider
Provider
Let’s start building!
Dart
dependencies:  flutter_riverpod: ^0.14.0+3
For providers to work, we must add ProviderScope at the root of our Flutter applications:
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';void main() {  runApp(ProviderScope(child: MyApp()));}
We’ll define all our global providers in this file so that it is easier to maintain the project. Let’s define a StateProvider.
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';  // Instead of String you can use other data types as well,// we can also use custom data types.final userNameProvider=StateProvider<String>((ref) {     // we can also return an empty String here, for the sake of simplicity,   //  let's return a sample name   return "SomeName";});
Now that we’ve declared a StateProvider, Let’s learn how can we read providers. To read any provider, we have multiple widgets, in this article, we’ll go over ConsumerWidget() and Consumer().
Using a ConsumerWidget():
A consumer widget is a widget that we can use in place of our Stateful/Stateless widget. It gives us the ability to read/change the states of providers & also listen to them.
Dart
import 'Providers/providers.dart' as providers.dart; //for easy accessimport 'package:flutter_riverpod/flutter_riverpod.dart';  class Home extends ConsumerWidget {  @override  Widget build(BuildContext context, ScopedReader watch) {          // Listens to the value exposed by userNameProvider    // ".state" method lets us get the state easily & directly    String name = watch(providers.userNameProvider).state;             return MaterialApp(      home: Scaffold(        appBar: AppBar(title: Center(child: const Text('GFG  '))),        body: Center(                      // displaying the value          child: Text('$name'),         ),      ),    );  }}
Output:
Well and good. So now, whenever the value of userNameProvider changes, the Text() will be updated accordingly. But how are we gonna update the value of userNameProvider?
To change the value of StateProvider, we need a StateController. So let’s create it.
First, let’s remove the variable “name”. ̶
S̶t̶r̶i̶n̶g̶ ̶n̶a̶m̶e̶ ̶=̶ ̶w̶a̶t̶c̶h̶(̶p̶r̶o̶v̶i̶d̶e̶r̶s̶.̶u̶s̶e̶r̶N̶a̶m̶e̶P̶r̶o̶v̶i̶d̶e̶r̶)̶.̶s̶t̶a̶t̶e̶;̶

//removed as with it we can only listen/get the state not change/mutate the state.

//let's use this StateController instead.

StateController<String> nameController = watch(providers.userNameProvider);

// now we can get / set the state using name.state.
For the sake of simplicity, let’s use a FloatingActionButton, and using it let’s try to mutate(change) the state of userNameProvider.
Dart
// for easy accessimport 'Providers/providers.dart' as providers.dart;  class Home extends ConsumerWidget {  int n = 0;  @override  Widget build(BuildContext context, ScopedReader watch) {          // Listens to the value exposed by userNameProvider    StateController<String> nameController = watch(providers.userNameProvider);      // ".state" method lets us get the state easily & directly    return MaterialApp(      home: Scaffold(        floatingActionButton: FloatingActionButton(          onPressed: () {            n++;            nameController.state =                                // now we can set the state using the state setter.                "New Name $n";           },        ),        appBar: AppBar(title: Center(child: const Text('GFG  '))),        body: Center(          child: Text(                          // displaying the value            '${nameController.state}',          ),         ),      ),    );  }}
Output:
Now, when we press on the FloatingActionButton(), we’ll see that the name changes every time with a different number. So that’s it. Using this information we can easily use StateProviders, mutate their values and listen to it.
Now, Let’s go over asynchronous providers.
When we work with asynchronous code we often use some Future based APIs, let’s look into how you can handle Future events using Riverpod. To demonstrate, we will create a method which returns a Future.
Dart
class FutureClass {      // in production, the below method could be any network call  Future<int> getData(String para) async {    await Future.delayed(Duration(seconds: 5));    return 25;  }}
Creating a provider for FutureClass:
final futureClass = Provider((ref) => FutureClass());
Now, let’s create our FutureProvider,
Dart
final response = FutureProvider<int>((ref) async {  final client = ref.read(futureClass);  return client.getData('some text as a parameter');});
Now, we can use ConsumerWidget to listen to the state changes and update our UI accordingly.
Apart from the ConsumerWidget(), we also have a Consumer() widget. The difference is that while ConsumerWidget rebuilds the entire screen when state changes, Consumer() rebuilds only it’s child, making sure we don’t run into performance issues.
Let’s use Consumer() widget to watch(listen) for state changes.
Dart
class HomeScreen extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Scaffold(    appBar: AppBar(title: Text("FutureProvider Demo"),),      body: Center(        child: Column(         children: [            Consumer(              builder: (context, watch, child) {                final futureData = watch(response);                             },            ),          ],        ),      ),    );  }}
As a Future can have 3 states, i.e, completed, in progress & error, we have the ability to handle those states separately using the .map function.
Dart
Consumer(  builder: (context, watch, child) {    final futureData = watch(response);      return  futureData.map(              ,// have data      data: (data) => Text('${data.value}',)              ,// in progress      loading: (_) => CircularProgressIndicator()              // has an error      error: (message) => Text(message.error),    );  },),
If we want to pass a variable/object to the provider as an argument, then we can do that like-
Dart
final response=    FutureProvider.autoDispose.family<int, String>((ref, i_am_a_param) async {  final client = ref.read(futureClass);  return client.getData(i_am_a_param);});  // the family modifier is used to pass a String value,// which is used to call the get() method.// The modifier "autoDispose" destroys the state of a provider // when it is no longer used, even when the widget state is not yet dispose
Complete Code:
Dart
class HomeScreen extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      home: Scaffold(        appBar: AppBar(title: Text("FutureProvider Demo"),),        body: SafeArea(          child: Center(            child: Column(              mainAxisAlignment: MainAxisAlignment.center,              children: [                Consumer(                  builder: (context, watch, child) {                    final futureData = watch(providers.response);                      return futureData.map(                      data: (data) => Text(                        '${data.value}',                      ), // have data                      loading: (_) => Column(                        children: [                          CircularProgressIndicator(),                          Text(                            'Fetching data',                          ),                        ],                      ), // in progress                      error: (message) =>                          Text(message.error.toString()),                      // has an error                    );                  },                ),              ],            ),          ),        ),      ),    );  }}
Output:
We often use streams in a flutter application, be it fetching data from Firestore or reading data from a file. Let’s learn how we can handle those streams with Riverpod.
Let’s declare a stream first,
final streamProvider = StreamProvider<int>((ref) {
  return Stream.fromIterable([105, 50]);
  //in production this could be a stream of documents from Firestore
});
This time, let’s use ConsumerWidget() to consume StreamProvider’s data, it is fairly similar to that of FutureProvider.
Dart
class HomeScreen extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      home: Scaffold(        appBar: AppBar(          title: Text("StreamProvider Demo"),        ),        body: SafeArea(          child: Center(            child: Column(              mainAxisAlignment: MainAxisAlignment.center,              children: [                Consumer(                  builder: (context, watch, child) {                    final streamValue = watch(providers.streamProvider);                      return streamValue.when(                      data: (data) => Text(                        '${data}',                      ), // have data                      loading: () => Column(                        children: [                          CircularProgressIndicator(),                          Text(                            'Fetching data',                          ),                        ],                      ), // in progress                      error: (message, e) =>                          Text(message.toString()),                      // has an error                    );                  },                ),              ],            ),          ),        ),      ),    );  }}
Now, every time the stream has new data, our UI will update accordingly.
Notice how we didn’t have to manage different states by ourselves, without Riverpod, the above code would look something like this:
Dart
final stream = Stream.fromIterable([105, 50]);  StreamBuilder<int>(  stream: stream,  builder: (context, snapshot) {    if (snapshot.connectionState == ConnectionState.active) {      if (snapshot.hasData) {                   // data        return SomeWidget(snapshot.data);      } else if (snapshot.hasError) {                  // error state        return SomeErrorWidget(snapshot.error);       } else {                  // no data        return Text('No data');       }    } else {               // loading state      return CircularProgressIndicator();    }  })
That’s it. This should cover almost everything we need to learn to get started with Riverpod .
The complete code for the article above can be found here.
Flutter
Android
Dart
Flutter
Android
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Flutter - Custom Bottom Navigation Bar
How to Read Data from SQLite Database in Android?
Retrofit with Kotlin Coroutine in Android
Android Listview in Java with Example
How to Change the Background Color After Clicking the Button in Android?
Listview.builder in Flutter
Flutter - DropDownButton Widget
Flutter - Asset Image
Flutter - Custom Bottom Navigation Bar
Splash Screen in Flutter