Flutter Riverpod状态管理完全指南

引言

状态管理是Flutter应用开发中最关键的问题之一。Riverpod作为新一代的状态管理库,以其简洁的API、强大的功能和良好的可测试性,正在成为越来越多开发者的首选。本文将深入探讨Riverpod的核心概念和实战技巧。

Riverpod基础

什么是Riverpod

Riverpod是由Flutter状态管理专家Remi Rousselet创建的状态管理库,旨在解决Provider的局限性,提供更灵活、更强大的状态管理方案。

核心优势

  1. 无Context依赖:可以在任何地方访问状态,不需要BuildContext
  2. 自动依赖注入:自动处理依赖关系,无需手动管理
  3. 更好的可测试性:轻松编写单元测试和Widget测试
  4. 类型安全:完整的类型推断支持
  5. 组合性强:可以轻松组合多个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是一个功能强大且易于使用的状态管理库,通过本文的学习,你应该掌握了:

  1. Provider类型:Provider、StateProvider、StateNotifierProvider等
  2. Provider组合:依赖、监听、派生状态
  3. 高级特性:生命周期、缓存、作用域
  4. 状态管理模式:Repository模式、MVVM模式
  5. 测试:单元测试和Widget测试
  6. 性能优化:autoDispose、select、keepAlive

核心要点:

  • 使用ref.watch()监听状态变化
  • 使用ref.read()读取不监听
  • 使用StateNotifier管理复杂状态逻辑
  • 使用FutureProviderStreamProvider处理异步操作
  • 通过组合provider构建复杂的状态管理系统

掌握Riverpod后,你可以构建更加清晰、可维护的Flutter应用状态管理方案。

更多推荐