
In front-end systems, “state management” is very important.
Previously introduced “StatefulWidget” also functions to manage state, but
when implementing an app with multiple screens and functions, managing the increasing state becomes difficult.
At such times, a great package that makes state management easy is Riverpod.
This time, I will introduce Riverpod, its overview, and basic usage.
The Riverpod introduced here is v2.
What is Riverpod?
Riverpod is a tool for defining global state.
If it’s a variable or a constant, you can define a global variable relatively easily by changing the scope of the definition.
However, the state used in front-end systems is closely related to widgets,
and widgets are rebuilt according to state changes, so it’s not that simple.
Moreover, it becomes even more complicated if you try to refer to the same state information in multiple widgets.
The tool that makes this easy is Riverpod.
Riverpod is not just a tool for using global variables,
but this time I am keeping the explanation basic.
About State Management
Before explaining Riverpod, I will explain state management.
State
Simply put, state is a variable that affects the information on the screen.
For example, user information when logged in, ToDo list information,
checkbox selection status, etc., these are data that affect screen display and are the state.
Widgets monitor this state and rebuild the screen whenever there is a change.

State Management
Updating, deleting, and expressing these states on the screen appropriately is called state management.
There are local states and global states.
- Local State
Used only in one screen or one widget.
Values such as text fields or counters that are only used on that screen. - Global State
State shared throughout the app.
User information displayed on the home screen or user information screen, etc.
Flutter provides “StatefulWidget” for state management.
However, managing global state with “StatefulWidget” becomes complex.
If there is a possibility of passing state to another screen or being updated by another screen,
you need to implement functions to synchronize the state before using it.
In Riverpod, the provider monitors the state and automatically communicates it to the affected widgets,
so there is no need to implement synchronization functions as mentioned above.
About Riverpod
Riverpod is an open-source package designed to make state management in Flutter easy.
It manages state using the following features.
- Notifier
Notifier manages the target state and the logic for updating and deleting it.
It has the role of updating the state and notifying the widget of state changes. - Provider
Widgets refer to and update the values of the Notifier (state) using the Provider.

Using Riverpod, the update of WidgetA is communicated to WidgetB as shown in the above image!
How to Use Riverpod (Overview)
When using Riverpod, prepare the following.
- State class
- Notifier
- Provider
For “3. Provider,” if it is version 2.0 or later, the “Riverpod Generator” will create it automatically.
Basic Usage of Riverpod
Introducing the basic usage with a ToDo app as an example.
Setting File
Add the following packages to pubspec.yaml.
dependencies:
flutter_riverpod:
riverpod_annotation:
freezed_annotation:
dev_dependencies:
build_runner:
riverpod_generator:
Riverpod-related Packages
flutter_riverpod: Package for using Riverpod in Flutter
riverpod_generator: Package that automatically defines Riverpod’s Provider
riverpod_annotation: Package that determines the annotations used in riverpod_generator
Other Packages
build_runner: Package that actually generates code automatically
freezed: Package for creating immutable classes
freezed_annotation: Package that determines the annotations used in freezed
Defining the State Class
The class used as the state must be an immutable class.
Define an immutable class using freezed, as shown below.
import 'package:freezed_annotation/freezed_annotation.dart';
part 'todo.freezed.dart';
@freezed // Annotation for using freezed
class Todo with _$Todo {
factory Todo({
required String id,
required String description,
required bool completed,
}) = _Todo;
}
What Are the Benefits of Being Immutable?
An immutable class is a class whose values cannot be updated.
In other words, once created, the values cannot be changed. It seems inconvenient, doesn’t it?
The data passing of instances of non-immutable classes is by “reference passing.”
When passing an instance to another class or function, you pass the address value where the value is stored, not the value itself.
If the instance passed as an argument to the class or function is updated,
the instance value at the caller’s end, which refers to that address, also changes.

Just with this, it seems usable for state management,
but the tricky thing about reference passing is that even instances passed for reading can be updated at the address value,
leading to unintended updates.
Moreover, these updates do not generate errors, making them undetectable.
Such unintended updates, for example, occur when sorting copied instances,
and the original copy gets sorted as well.

In Riverpod, we want to manage the state with immutable classes that can only be updated intentionally,
to prevent unintended changes like the ones mentioned above.
Defining the Notifier
Define the Notifier based on the class defined above.
Here, write the processing for updating and newly adding states.
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'todo.dart';
// The following steps will create this file when build runner is executed
part 'todo_notifier.g.dart';
@riverpod
class TodoNotifier extends _$TodoNotifier {
@override
List<Todo> build() {
return [];
}
// Add new
void addTodo(Todo todo) {
state = [...state, todo];
}
// Delete
void removeTodo(String todoId) {
state = [
for (final todo in state)
if (todo.id != todoId) todo,
];
}
// Toggle "completed" flag
void toggle(String todoId) {
state = [
for (final todo in state)
if (todo.id == todoId)
todo.copyWith(completed: !todo.completed)
else
todo,
];
}
}
This article is to explain the overview of Riverpod, so detailed processing explanations are omitted.
Running Riverpod Generator
Execute the following command in the terminal to run build_runner.
flutter pub run build_runner build --delete-conflicting-outputs
This will start the Riverpod Generator and create the ~.d.dart file.
The above command also starts freezed, so the ~.freezed.dart file will also be created.
Riverpod Generator creates the Provider.
Looking at the created ~.d.dart, you can see that the Provider is defined.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'todo_notifier.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$todoNotifierHash() => r'1330ee68f148d9ff18b1e0370e3f3541ab67c621';
@ProviderFor(TodoNotifier)
final todoNotifierProvider =
AutoDisposeNotifierProvider<TodoNotifier, List<Todo>>.internal(
TodoNotifier.new,
name: r'todoNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$todoNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$TodoNotifier = AutoDisposeNotifier<List<Todo>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
Widgets Using Riverpod
To use Riverpod, you need to declare the range of use.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'todo_item.dart';
import 'todo_notifier.dart';
void main() {
// Declare the scope of use of Riverpod with "ProviderScope"
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TodoItemListPage(),
);
}
}
// Define the widget using Riverpod with "ConsumerWidget"
class TodoItemListPage extends ConsumerWidget {
@override
// "WidgetRef ref" is the context for using the Provider
Widget build(BuildContext context, WidgetRef ref) {
// "ref.watch" monitors the Provider and detects state changes.
List<TodoItem> todoItems = ref.watch(todoNotifierProvider);
return Scaffold(
appBar: AppBar(title: Text('TodoItem List')),
body: ListView.builder(
itemCount: todoItems.length,
itemBuilder: (context, index) {
final todoItem = todoItems[index];
return ListTile(
title: Text(todoItem.title),
leading: Checkbox(
value: todoItem.completed,
onChanged: (bool? newValue) {
// "ref.read" calls the methods of the Notifier from the Provider.
ref.read(todoNotifierProvider.notifier).toggle(todoItem.id);
},
),
onLongPress: () => ref.read(todoNotifierProvider.notifier).removeTodo(todoItem.id),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// addTodo
},
child: Icon(Icons.add),
),
);
}
}
“ref” is an abbreviation for reference.
References to the state from the widget are ref→Provider→Notifier→State.
* The above process does not use the “addTodo” of the Notifier.
Execution Screen
When executed, the initial data is displayed as follows.
When you tap the checkbox or long-press the list item, the state changes.

Complete!!
Finally
This time, I explained the basics of Riverpod.
Since Riverpod has various features, I hope to introduce those features in the future.
I hope this article helps you understand Riverpod even a little.
Thank you for reading to the end!!