【Flutter Project】001-Flutter State Management: Riverpod
Article directory
- [Flutter Project] 001-Flutter State Management: Riverpod
- I. Overview
-
- 1. Official status management
- 2. State management solution
- 3. Why choose Riverpod
-
- Riverpod Official Documentation
- Several Providers provided by Riverpod
- 2. Official example
-
- 1. Installation
- 2. Official example
- 3. Code generation
- 4. Official sample running results
- 3. Basic use
-
- 1. Transform `main.dart`
- 2. Create `home_page.dart`
- 3. Create `hello_state.dart`
- 4. Running results
- Fourth, use code generation
-
- 1. Transform `hello_state.dart`
- 2. Code generation
- 3. Transform `home_page.dart`
- 4. Running results
- 5. Why use code generation in Riverpod
1. Overview
1. Official status management
State management deals with the key concepts of data flow and UI updates in an application. In a Flutter application, state management ensures that application UI and data stay in sync, shares and synchronizes data, and provides good code structure and maintainability.
Flutter provides StatefulWidget
as the most basic state management method. Stateful components can store and update their own state, suitable for simple scenarios and local states.
However, StatefulWidget
has the following problems:
- State Management Complexity: When the component tree is huge and the state needs to be shared among multiple components, state management becomes complicated and the code is difficult to understand and maintain.
- Performance issue: Compared with
StatelessWidget
,StatefulWidget
will cause more components to rebuild when the state changes, which may affect application performance, although Flutter has Optimized for performance. - Lifecycle Management Complexity:
StatefulWidget
has a complex lifecycle and needs to handle multiple lifecycle methods (likeinitState
,didUpdateWidget
and dispose), resulting in complex and unmanageable code. - Difficult to test: Since
StatefulWidget
has internal state, writing unit and integration tests becomes more difficult, potentially affecting the quality and reliability of your application. - Poor reusability: The state of a
StatefulWidget
is often tightly coupled to a specific instance, reducing the reusability of the component.
2. Status management solution
In Flutter, there are other state management methods to choose from, the following are some common state management methods.
- InheritedWidget and InheritedModel: These are special types of components provided by Flutter that allow state to be passed down the component tree. They help you share state between different layers of your application. This approach is
suitable
for smaller applications or limited state sharing needs. - Provider: A third-party library for dependency injection and state management. It is encapsulated on the basis of InheritedWidget. It has the capabilities of the above components, but it is simpler and easier to use. Providers can listen for state changes and rebuild associated components when needed. This approach is suitable for
applications of all sizes
and is scalable and flexible. - Riverpod: A relatively new state management library, similar to Provider, but offering more features and improvements. Riverpod allows you to create immutable, composable, and testable state management solutions. This approach is suitable for
applications that require a higher degree of controllability and testability
. - BLoC (Business Logic Component): A state management method based on reactive programming. BLoC separates business logic from UI, allowing you to easily test and reuse code. BLoC is often used with RxDart, a reactive programming library for Dart, to provide powerful data flow processing capabilities. This approach is suitable for
applications that need to handle complex business logic and large data streams
. - Redux: A centralized state management library that stores the state of an application in a single state tree. Redux uses pure functions (called reducers) to handle state updates, allowing you to easily track and manage your application’s state changes. This approach is suitable for
applications that require strict state management and predictability
.
The specific state management method you choose depends on the needs, complexity, and personal preferences of your application. Different methods have different advantages and disadvantages, so when choosing a state management method, it is important to fully understand the characteristics of each method and weigh its applicability.
3. Why choose Riverpod
The reason is that some of the main features of Riverpod are more powerful, which fits our needs, and listen to me slowly…
- Immutability. The state in Riverpod is immutable, which means that when the state is updated, a new object is created instead of modifying the existing object. This helps reduce errors and makes the state easier to understand and track.
- Type safety. Riverpod provides stronger type safety at compile time, helping to reduce type errors and improve code quality.
- No BuildContext required. Unlike Providers, Riverpod does not rely on the BuildContext to access state. This makes it easier to access state in places outside of the component, such as functions or classes, while improving testability.
- Composable. Riverpod allows you to combine different Providers to create more complex state management solutions. This helps keep the code modular and maintainable.
- Easy to test. Since Riverpod’s state doesn’t depend on the BuildContext, you can write unit tests more easily. Additionally, Riverpod provides utilities for simulating state and testing.
- Family Features. Riverpod has a so-called “family” feature that allows you to create multiple Provider instances of the same type based on parameters. This allows for better state management when using multiple components with the same logic but different parameters.
- Very flexible. Riverpod is highly flexible and adapts well to different application structures and needs. You can use Riverpod to build simple local state management, or complex global state management solutions.
In conclusion, Riverpod is a powerful state management library for Flutter applications of all sizes. It offers immutability, type safety, BuildContext-free access, composability, ease of testing, and family capabilities. If you’re looking for a modern, flexible, and easy-to-use state management solution, Riverpod is an option worth considering.
Riverpod Official Documentation
https://docs-v2.riverpod.dev/zh-hans/
Several Providers provided by Riverpod
2. Official example
1. Installation
flutter pub add flutter_riverpod dev:custom_lint dev:riverpod_lint riverpod_annotation dev:build_runner dev:riverpod_generator
2. Official example
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'main.g.dart'; // We create a "provider" that will hold a value (here "Hello world"). // By using a provider, we can mock or override exposed values. @riverpod String helloWorld(HelloWorldRef ref) {<!-- --> return 'Hello world'; } void main() {<!-- --> runApp( // In order for the component to read the provider, we need to pass the entire // Applications are wrapped in a "ProviderScope" component. // This is where all our provider state is stored. ProviderScope( child: MyApp(), ), ); } // Extend HookConsumerWidget from Riverpod instead of HookWidget class MyApp extends ConsumerWidget {<!-- --> @override Widget build(BuildContext context, WidgetRef ref) {<!-- --> final String value = ref. watch(helloWorldProvider); return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Example')), body: Center( // read the value of provider // Here, for easy viewing, a large font is set child: Text(value, style: const TextStyle(fontSize: 40),), ), ), ); } }
3. Code generation
# --delete-conflicting-outputs is optional, when generating code conflicts, delete the original code and regenerate flutter pub run build_runner build --delete-conflicting-outputs
4. Official sample running results
3. Basic usage
1. Transform main.dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:study/pages/HomePage.dart'; void main() {<!-- --> runApp( // In order for the component to read the provider, we need to pass the entire // Applications are wrapped in a "ProviderScope" component. // This is where all our provider state is stored. const ProviderScope( child: MyApp(), ), ); } class MyApp extends StatelessWidget {<!-- --> const MyApp({<!-- -->super.key}); @override Widget build(BuildContext context) {<!-- --> return const MaterialApp( home: HomePage(), ); } }
2. Create home_page.dart
/lib/pages/home_page.dart
import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../states/hello_state.dart'; class HomePage extends HookConsumerWidget {<!-- --> const HomePage({<!-- -->super.key}); @override Widget build(BuildContext context, WidgetRef ref) {<!-- --> final hello = ref. watch(helloStateProvider); return Scaffold( appBar: AppBar( title: const Text('Home'), ), body: Center( child: SizedBox( height: 400, child: Column( children: [ // text Text(hello. hello, style: const TextStyle(fontSize: 40),), // update text ElevatedButton( style: ButtonStyle(minimumSize: MaterialStateProperty. all(const Size(200, 50))), onPressed: () {<!-- --> ref.read(helloStateProvider.notifier).setHello("The text has been updated!"); }, child: const Text('Update'), ), ], ), )), ); } }
3. Create hello_state.dart
lib/state/hello_state.dart
import 'package:flutter_riverpod/flutter_riverpod.dart'; class HelloState {<!-- --> final String hello; HelloState({<!-- --> this. hello = 'Hello World', }); } class HelloStateProvider extends StateNotifier<HelloState> {<!-- --> HelloStateProvider() : super(HelloState()); void setHello(String hello) {<!-- --> state = HelloState( hello: hello, ); } } final helloStateProvider = StateNotifierProvider<HelloStateProvider, HelloState>( (ref) => HelloStateProvider(), );
4. Running results
4. Using code generation
Code generation refers to using tools to generate code for us.
In Dart, it has the disadvantage of requiring an extra step to “compile” the application. Although this problem may be fixed in the near future, the Dart team is researching and solving potential solutions to this problem.
Code generation is completely optional when using Riverpod. You can of course not use it at all.
In the meantime, Riverpod supports code generation and recommends that you use it.
1. Transform hello_state.dart
import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'hello_state.g.dart'; @riverpod class HelloList extends _$HelloList {<!-- --> @override List<String> build() {<!-- --> return ["hello world!"]; } void addHello(String hello) {<!-- --> state = [...state, hello]; } }
2. Code generation
# --delete-conflicting-outputs is optional, when generating code conflicts, delete the original code and regenerate flutter pub run build_runner build --delete-conflicting-outputs
3. Transform home_page.dart
import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../states/hello_state.dart'; class HomePage extends HookConsumerWidget {<!-- --> const HomePage({<!-- -->super.key}); @override Widget build(BuildContext context, WidgetRef ref) {<!-- --> final List<String> hellos = ref. watch(helloListProvider); return Scaffold( appBar: AppBar( title: const Text('Home'), ), body: Center( child: SizedBox( height: 400, child: Column( children: [ // text: loop through the hellos list ...hellos. map((e) => Text(e, style: const TextStyle(fontSize: 40),)), // update text ElevatedButton( style: ButtonStyle(minimumSize: MaterialStateProperty. all(const Size(200, 50))), onPressed: () {<!-- --> // get the current time DateTime now = DateTime. now(); ref.read(helloListProvider.notifier).addHello("hello ${now.second}"); }, child: const Text('Update'), ), ], ), )), ); } }
4. Running results
5. Why use code generation in Riverpod
You might be thinking: “If code generation is optional in Riverpod, why use it?”
Make your code life easier.
This includes but is not limited to:
- Better syntax, more readable and flexible, plus a reduced learning curve.
- No need to worry about
FutureProvider
,Provider
or other providers. Just write your logic, and Riverpod will choose the most suitable provider for you. - Passing parameters to providers is now unrestricted. No longer limited to using family and passing a single parameter, any form of parameter can now be passed. This includes named parameters, optional parameters and even default values.
- No need to worry about
- Code written in Riverpod supports stateful hot reload.
- Better debugging, by generating additional metadata and then debugging with a debugger.
- Some features of Riverpod will only support code generation.
At the same time, code generation like Freezed or json_serializable is already used in many applications. In this case, your project is probably already configured for code generation, and using Riverpod should be straightforward.