Managing state in a Flutter application can be complex, especially as the app grows. Riverpod is a state management library that offers several advantages over other state management solutions like Provider, Bloc, or Redux. In this tutorial, we will learn how to manage state in a Flutter application using Riverpod, create a counter app with increment, decrement, and reset functionalities, and navigate to a second page while maintaining state across pages.
Riverpod is a modern state management library for Flutter that provides several benefits:
BuildContext
to access providers, making it easier to use.StateNotifierProvider
and StateProvider
to manage global state that needs to be accessed across the app.ProviderScope
and overrides
to inject mock providers in tests.Consumer
and ConsumerWidget
to rebuild only the parts of the UI that depend on specific providers.flutter create counter_app
cd counter_app
pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.0.0
flutter pub get
to install the dependencies.Create a file named counter_provider.dart
in the lib
directory:
import 'package:flutter_riverpod/flutter_riverpod.dart';
// StateNotifier for managing the counter
class CounterNotifier extends StateNotifier {
CounterNotifier() : super(0); // Initialize the counter to 0
// Method to increment the counter by a given step
void increment(int step) => state += step;
// Method to decrement the counter if it is greater than 0
void decrement() {
if (state > 0) state--;
}
// Method to reset the counter to 0
void reset() => state = 0;
}
// Provider for the counter
final counterProvider = StateNotifierProvider((ref) {
return CounterNotifier();
});
// StateNotifier for managing the step size
class StepNotifier extends StateNotifier {
StepNotifier() : super(1); // Initialize the step size to 1
// Method to set a new step size
void setStep(int newStep) => state = newStep;
}
// Provider for the step size
final stepProvider = StateNotifierProvider((ref) {
return StepNotifier();
});
Update the main.dart
file in the lib
directory:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_provider.dart'; // Ensure this is the correct import path
import 'second_page.dart'; // Import the second page
void main() {
runApp(const ProviderScope(child: MyApp())); // Wrap MyApp with ProviderScope
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends ConsumerStatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
ConsumerState createState() => _MyHomePageState();
}
class _MyHomePageState extends ConsumerState
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 50),
vsync: this,
);
_animation = CurvedAnimation(parent: _controller, curve: Curves.bounceOut);
}
void _incrementCounter() {
final step = ref.read(stepProvider);
ref.read(counterProvider.notifier).increment(step);
_controller.forward().then((_) => _controller.reverse());
}
void _decrementCounter() {
ref.read(counterProvider.notifier).decrement();
_controller.forward().then((_) => _controller.reverse());
}
void _resetCounter() {
ref.read(counterProvider.notifier).reset();
_controller.reset();
}
void _navigateToSecondPage() {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const SecondPage()),
);
}
@override
Widget build(BuildContext context) {
final counter = ref.watch(counterProvider);
final step = ref.watch(stepProvider);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: _animation.value + 1,
child: child,
);
},
child: Text(
'$counter',
style: Theme.of(context).textTheme.headlineMedium,
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton.extended(
onPressed: counter > 0 ? _decrementCounter : null,
tooltip: 'Decrement',
icon: const Icon(Icons.remove),
label: const Text('Decrement'),
backgroundColor: counter > 0 ? Colors.blue : Colors.grey,
),
const SizedBox(width: 20),
FloatingActionButton.extended(
onPressed: _resetCounter,
tooltip: 'Reset',
icon: const Icon(Icons.refresh),
label: const Text('Reset'),
),
const SizedBox(width: 20),
FloatingActionButton.extended(
onPressed: _incrementCounter,
tooltip: 'Increment',
icon: const Icon(Icons.add),
label: const Text('Increment'),
),
],
),
const SizedBox(height: 20),
Text('Increment Step: $step'),
Slider(
value: step.toDouble(),
min: 1,
max: 10,
divisions: 9,
label: step.toString(),
onChanged: (value) {
ref.read(stepProvider.notifier).setStep(value.toInt());
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _navigateToSecondPage,
child: const Text('Go to Second Page'),
),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Create a new file named second_page.dart
in the lib
directory:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_provider.dart'; // Ensure this is the correct import path
class SecondPage extends ConsumerWidget {
const SecondPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
void _incrementCounter() {
final step = ref.read(stepProvider);
ref.read(counterProvider.notifier).increment(step);
}
void _decrementCounter() {
ref.read(counterProvider.notifier).decrement();
}
return Scaffold(
appBar: AppBar(
title: const Text('Second Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Counter on second page:'),
Text(
'$counter',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton.extended(
onPressed: _decrementCounter,
tooltip: 'Decrement',
icon: const Icon(Icons.remove),
label: const Text('Decrement'),
),
const SizedBox(width: 20),
FloatingActionButton.extended(
onPressed: _incrementCounter,
tooltip: 'Increment',
icon: const Icon(Icons.add),
label: const Text('Increment'),
),
],
),
],
),
),
);
}
}
flutter run
In this tutorial, we learned how to manage state in a Flutter application using Riverpod. We created a simple counter app with increment, decrement, and reset functionalities. Additionally, we added a slider to adjust the increment step size, animated the counter text with a bounce effect, and added navigation to a second page where the counter can also be modified. The state is maintained across pages, demonstrating the power and flexibility of Riverpod for state management in Flutter applications.