Flutter Riverpod状态管理完全指南
·
Flutter Riverpod状态管理完全指南
引言
状态管理是Flutter应用开发中最关键的问题之一。Riverpod作为新一代的状态管理库,以其简洁的API、强大的功能和良好的可测试性,正在成为越来越多开发者的首选。本文将深入探讨Riverpod的核心概念和实战技巧。
Riverpod基础
什么是Riverpod
Riverpod是由Flutter状态管理专家Remi Rousselet创建的状态管理库,旨在解决Provider的局限性,提供更灵活、更强大的状态管理方案。
核心优势
- 无Context依赖:可以在任何地方访问状态,不需要BuildContext
- 自动依赖注入:自动处理依赖关系,无需手动管理
- 更好的可测试性:轻松编写单元测试和Widget测试
- 类型安全:完整的类型推断支持
- 组合性强:可以轻松组合多个provider
安装与配置
在pubspec.yaml中添加依赖:
dependencies:
flutter_riverpod: ^2.3.0
在应用入口配置ProviderScope:
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
ProviderScope(
child: const MyApp(),
),
);
}
Provider类型详解
1. Provider - 只读状态
// 创建一个简单的provider
final greetingProvider = Provider<String>((ref) {
return 'Hello Riverpod!';
});
// 使用provider
class GreetingWidget extends ConsumerWidget {
const GreetingWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final greeting = ref.watch(greetingProvider);
return Text(greeting);
}
}
2. StateProvider - 可变更状态
// 创建可变更的provider
final counterProvider = StateProvider<int>((ref) => 0);
// 使用StateProvider
class CounterWidget extends ConsumerWidget {
const CounterWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).state++;
},
child: const Text('Increment'),
),
],
);
}
}
3. StateNotifierProvider - 复杂状态逻辑
// 定义状态类
class Todo {
const Todo({
required this.id,
required this.title,
this.completed = false,
});
final String id;
final String title;
final bool completed;
Todo copyWith({
String? id,
String? title,
bool? completed,
}) {
return Todo(
id: id ?? this.id,
title: title ?? this.title,
completed: completed ?? this.completed,
);
}
}
// 定义StateNotifier
class TodoNotifier extends StateNotifier<List<Todo>> {
TodoNotifier() : super([]);
void addTodo(String title) {
state = [
...state,
Todo(
id: DateTime.now().toString(),
title: title,
),
];
}
void toggleTodo(String id) {
state = state.map((todo) {
if (todo.id == id) {
return todo.copyWith(completed: !todo.completed);
}
return todo;
}).toList();
}
void removeTodo(String id) {
state = state.where((todo) => todo.id != id).toList();
}
}
// 创建StateNotifierProvider
final todoProvider = StateNotifierProvider<TodoNotifier, List<Todo>>(
(ref) => TodoNotifier(),
);
4. FutureProvider - 异步状态
// 创建FutureProvider
final userProvider = FutureProvider<User>((ref) async {
final response = await http.get(Uri.parse('https://api.example.com/user'));
return User.fromJson(json.decode(response.body));
});
// 使用FutureProvider
class UserWidget extends ConsumerWidget {
const UserWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsyncValue = ref.watch(userProvider);
return userAsyncValue.when(
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error: $error'),
data: (user) => Text('User: ${user.name}'),
);
}
}
5. StreamProvider - 流式状态
// 创建StreamProvider
final counterStreamProvider = StreamProvider<int>((ref) {
return Stream.periodic(const Duration(seconds: 1), (count) => count);
});
// 使用StreamProvider
class StreamCounterWidget extends ConsumerWidget {
const StreamCounterWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterStreamProvider);
return counter.when(
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error: $error'),
data: (count) => Text('Count: $count'),
);
}
}
6. ChangeNotifierProvider - 兼容ChangeNotifier
// 定义ChangeNotifier
class ThemeNotifier extends ChangeNotifier {
bool _isDarkMode = false;
bool get isDarkMode => _isDarkMode;
void toggleTheme() {
_isDarkMode = !_isDarkMode;
notifyListeners();
}
}
// 创建ChangeNotifierProvider
final themeProvider = ChangeNotifierProvider<ThemeNotifier>(
(ref) => ThemeNotifier(),
);
// 使用ChangeNotifierProvider
class ThemeWidget extends ConsumerWidget {
const ThemeWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themeProvider);
return Switch(
value: theme.isDarkMode,
onChanged: (value) {
ref.read(themeProvider).toggleTheme();
},
);
}
}
Provider组合
1. 依赖其他Provider
// 基础provider
final apiClientProvider = Provider<ApiClient>((ref) => ApiClient());
// 依赖apiClientProvider的provider
final userRepositoryProvider = Provider<UserRepository>((ref) {
final apiClient = ref.watch(apiClientProvider);
return UserRepository(apiClient: apiClient);
});
// 依赖userRepositoryProvider的FutureProvider
final currentUserProvider = FutureProvider<User>((ref) async {
final repository = ref.watch(userRepositoryProvider);
return repository.getCurrentUser();
});
2. 监听Provider变化
final counterProvider = StateProvider<int>((ref) => 0);
class CounterListener extends ConsumerWidget {
const CounterListener({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 监听counter变化并执行副作用
ref.listen<int>(counterProvider, (previous, current) {
if (current > 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Count exceeds 10!')),
);
}
});
final count = ref.watch(counterProvider);
return Text('Count: $count');
}
}
3. 选择监听部分状态
// 使用select只监听状态的一部分
final userProvider = StateNotifierProvider<UserNotifier, User>(...);
class UserNameWidget extends ConsumerWidget {
const UserNameWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 只监听name字段变化
final userName = ref.watch(userProvider.select((user) => user.name));
return Text('Name: $userName');
}
}
4. 派生状态
final todosProvider = StateNotifierProvider<TodoNotifier, List<Todo>>(
(ref) => TodoNotifier(),
);
// 派生状态:未完成的todo数量
final incompleteTodosCountProvider = Provider<int>((ref) {
final todos = ref.watch(todosProvider);
return todos.where((todo) => !todo.completed).length;
});
// 派生状态:是否所有todo都已完成
final allTodosCompletedProvider = Provider<bool>((ref) {
final todos = ref.watch(todosProvider);
return todos.isNotEmpty && todos.every((todo) => todo.completed);
});
高级特性
1. Provider生命周期
final lifecycleProvider = Provider<String>((ref) {
// 在provider创建时执行
ref.onDispose(() {
// 在provider销毁时执行清理操作
print('Provider disposed');
});
// 监听依赖变化
ref.listenSelf((previous, next) {
print('Value changed from $previous to $next');
});
return 'Initial value';
});
2. 缓存与刷新
// 使用keepAlive保持provider存活
final cachedProvider = Provider<String>((ref) {
ref.keepAlive();
return 'Cached value';
});
// 使用refresh重新获取
final userProvider = FutureProvider<User>((ref) async {
return fetchUser();
});
class RefreshButton extends ConsumerWidget {
const RefreshButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () {
// 刷新provider
ref.refresh(userProvider);
},
child: const Text('Refresh'),
);
}
}
3. 延迟初始化
final lazyProvider = Provider<String>((ref) {
// 这个代码只会在provider首次被监听时执行
print('Provider initialized');
return 'Lazy value';
});
4. 作用域Provider
// 创建一个在特定作用域内有效的provider
final scopedProvider = Provider<String>((ref) => 'Scoped value');
class ScopedWidget extends ConsumerWidget {
const ScopedWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return ProviderScope(
overrides: [
scopedProvider.overrideWithValue('Overridden value'),
],
child: const ChildWidget(),
);
}
}
状态管理模式
模式1:Repository模式
// Repository接口
abstract class UserRepository {
Future<User> getUser(String id);
Future<List<User>> getUsers();
}
// Repository实现
class UserRepositoryImpl implements UserRepository {
final ApiClient apiClient;
UserRepositoryImpl({required this.apiClient});
@override
Future<User> getUser(String id) async {
final response = await apiClient.get('/users/$id');
return User.fromJson(response.data);
}
@override
Future<List<User>> getUsers() async {
final response = await apiClient.get('/users');
return (response.data as List).map((e) => User.fromJson(e)).toList();
}
}
// Provider定义
final apiClientProvider = Provider<ApiClient>((ref) => ApiClient());
final userRepositoryProvider = Provider<UserRepository>((ref) {
final apiClient = ref.watch(apiClientProvider);
return UserRepositoryImpl(apiClient: apiClient);
});
模式2:MVVM模式
// ViewModel
class LoginViewModel extends StateNotifier<LoginState> {
final AuthRepository authRepository;
LoginViewModel({required this.authRepository}) : super(const LoginState.idle());
Future<void> login(String email, String password) async {
state = const LoginState.loading();
try {
await authRepository.login(email, password);
state = const LoginState.success();
} catch (e) {
state = LoginState.error(e.toString());
}
}
}
// State定义
sealed class LoginState {
const LoginState();
const factory LoginState.idle() = _Idle;
const factory LoginState.loading() = _Loading;
const factory LoginState.success() = _Success;
const factory LoginState.error(String message) = _Error;
}
class _Idle extends LoginState {
const _Idle();
}
class _Loading extends LoginState {
const _Loading();
}
class _Success extends LoginState {
const _Success();
}
class _Error extends LoginState {
const _Error(this.message);
final String message;
}
// Provider
final loginViewModelProvider = StateNotifierProvider<LoginViewModel, LoginState>(
(ref) => LoginViewModel(
authRepository: ref.watch(authRepositoryProvider),
),
);
模式3:异步状态处理
// 封装异步状态
final fetchDataProvider = FutureProvider.autoDispose<String>((ref) async {
await Future.delayed(const Duration(seconds: 2));
// 模拟错误
// throw Exception('Failed to fetch data');
return 'Fetched data successfully';
});
class DataWidget extends ConsumerWidget {
const DataWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final dataAsync = ref.watch(fetchDataProvider);
return dataAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stackTrace) => Center(
child: Column(
children: [
Text('Error: $error'),
ElevatedButton(
onPressed: () => ref.refresh(fetchDataProvider),
child: const Text('Retry'),
),
],
),
),
data: (data) => Center(child: Text(data)),
);
}
}
测试
单元测试
void main() {
group('TodoNotifier', () {
test('initial state is empty list', () {
final notifier = TodoNotifier();
expect(notifier.state, equals([]));
});
test('addTodo adds a todo', () {
final notifier = TodoNotifier();
notifier.addTodo('Test todo');
expect(notifier.state.length, equals(1));
expect(notifier.state[0].title, equals('Test todo'));
expect(notifier.state[0].completed, isFalse);
});
test('toggleTodo toggles completion status', () {
final notifier = TodoNotifier();
notifier.addTodo('Test todo');
final todoId = notifier.state[0].id;
notifier.toggleTodo(todoId);
expect(notifier.state[0].completed, isTrue);
notifier.toggleTodo(todoId);
expect(notifier.state[0].completed, isFalse);
});
});
}
Widget测试
void main() {
testWidgets('Counter increments when button is pressed', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(home: const CounterWidget()),
),
);
expect(find.text('Count: 0'), findsOneWidget);
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
expect(find.text('Count: 1'), findsOneWidget);
});
}
性能优化
1. 使用autoDispose清理资源
// 自动清理的FutureProvider
final userProvider = FutureProvider.autoDispose<User>((ref) async {
return fetchUser();
});
// 自动清理的StateNotifierProvider
final todoProvider = StateNotifierProvider.autoDispose<TodoNotifier, List<Todo>>(
(ref) => TodoNotifier(),
);
2. 使用select减少重建
// 只监听需要的字段
final userName = ref.watch(userProvider.select((user) => user.name));
3. 使用keepAlive缓存
// 保持provider存活
final cachedProvider = Provider<String>((ref) {
ref.keepAlive();
return 'Cached';
});
4. 避免不必要的监听
// 只读取不监听
final notifier = ref.read(counterProvider.notifier);
// 使用listen而不是watch
ref.listen(counterProvider, (previous, current) {
// 只在变化时执行
});
常见问题
问题1:Provider未找到
解决方案:
- 确保在
ProviderScope下使用 - 检查provider名称拼写
- 确保provider在使用前已定义
问题2:状态不更新
解决方案:
- 使用
ref.watch()而不是ref.read()来监听状态 - 确保StateNotifier正确调用
state =来更新状态 - 检查是否使用了正确的notifier
问题3:性能问题
解决方案:
- 使用
select只监听需要的状态 - 使用
autoDispose自动清理 - 避免在build方法中进行耗时操作
总结
Riverpod是一个功能强大且易于使用的状态管理库,通过本文的学习,你应该掌握了:
- Provider类型:Provider、StateProvider、StateNotifierProvider等
- Provider组合:依赖、监听、派生状态
- 高级特性:生命周期、缓存、作用域
- 状态管理模式:Repository模式、MVVM模式
- 测试:单元测试和Widget测试
- 性能优化:autoDispose、select、keepAlive
核心要点:
- 使用
ref.watch()监听状态变化 - 使用
ref.read()读取不监听 - 使用
StateNotifier管理复杂状态逻辑 - 使用
FutureProvider和StreamProvider处理异步操作 - 通过组合provider构建复杂的状态管理系统
掌握Riverpod后,你可以构建更加清晰、可维护的Flutter应用状态管理方案。
更多推荐
所有评论(0)