深入探讨 Flutter 的 Riverpod 状态管理

1,414 阅读6分钟
  1. Riverpod 概述

(1) 什么是 Riverpod

  • 定义:Riverpod 是一个现代化的 Flutter 状态管理库,由 Rémi Rousselet 开发,作为 Provider 包的升级替代品。

  • 核心特点:

    • 无上下文依赖:不像 Provider 依赖 BuildContext,Riverpod 使用 WidgetRef 或 Ref 访问状态。
    • 类型安全:编译时检查 Provider 类型,减少运行时错误。
    • 模块化:支持多种 Provider 类型(如 Provider、StateNotifierProvider、AsyncNotifierProvider)。
    • 可测试性:无需模拟 BuildContext,易于单元测试。
    • 灵活性:支持异步状态、组合 Provider 和全局/局部作用域。
  • 与 Provider 的区别:

    • Provider 基于 InheritedWidget,需要 context.watch/context.read。
    • Riverpod 使用 ProviderScope,通过 ref 访问状态,无需上下文。
    • Riverpod 提供更丰富的 API(如 select、family),支持复杂场景。

(2) 核心概念

  • Provider:声明状态的单元(如值、对象、状态机)。
  • Ref:访问和操作 Provider 的接口,类似 BuildContext 的替代品。
  • ProviderScope:Riverpod 的作用域容器,管理 Provider 的生命周期。
  • ConsumerWidget:订阅 Provider 的 Widget,类似 Consumer。
  • Notifier:管理状态的类(如 StateNotifier、AsyncNotifier)。

(3) 适用场景

  • 中小型应用:简单状态管理(如主题、计数器)。
  • 复杂应用:多模块、异步操作(如网络请求、实时数据)。
  • 高可测试性需求:需要单元测试和 mock 数据。
  • 跨平台一致性:结合 Clean Architecture,管理 UI 和业务逻辑。

  1. 源码分析:Riverpod 核心实现

为了深入理解 Riverpod 的工作原理,我们分析 package:riverpod 和 package:flutter_riverpod 的核心源码,聚焦 Provider、StateNotifierProvider 和 ProviderScope。

(1) Provider

  • 源码位置:package:riverpod/lib/src/provider.dart

  • 关键代码:

    dart

    class Provider<T> extends AlwaysAliveProviderBase<T, T> {
      Provider(this._create, {String? name}) : super(name);
    
      final Create<T, ProviderRef<T>> _create;
    
      @override
      T get state => _create(_ref!);
    }
    
  • 分析:

    • Provider 是一个不可变状态的容器,通过 _create 函数生成值。
    • 继承 AlwaysAliveProviderBase,表示 Provider 始终存活(除非被 override 或销毁)。
    • _ref 是 ProviderRef,提供访问其他 Provider 或控制生命周期的接口。
  • 用途:存储常量、依赖注入(如服务、配置)。

(2) StateNotifierProvider

  • 源码位置:package:riverpod/lib/src/state_notifier.dart

  • 关键代码:

    dart

    class StateNotifierProvider<Notifier extends StateNotifier<T>, T>
        extends ProviderBase<StateNotifier<T>, T> {
      StateNotifierProvider(this._create, {String? name}) : super(name);
    
      final Create<Notifier, Ref<StateNotifier<T>>> _create;
    
      @override
      T get state => _create(_ref!).state;
    }
    
    abstract class StateNotifier<T> extends ChangeNotifier {
      T _state;
      StateNotifier(this._state);
    
      T get state => _state;
      set state(T value) {
        if (_state != value) {
          _state = value;
          notifyListeners();
        }
      }
    }
    
  • 分析:

    • StateNotifierProvider 管理 StateNotifier 实例,暴露其 state。
    • StateNotifier 是一个可变状态容器,使用 notifyListeners 通知 UI 更新。
    • _create 初始化 Notifier,允许依赖注入。
  • 用途:管理动态状态(如计数器、表单)。

(3) ProviderScope

  • 源码位置:package:flutter_riverpod/lib/src/provider_scope.dart

  • 关键代码:

    dart

    class ProviderScope extends InheritedWidget {
      ProviderScope({
        required this.container,
        required Widget child,
      }) : super(child: child);
    
      final ProviderContainer container;
    
      @override
      bool updateShouldNotify(ProviderScope oldWidget) {
        return container != oldWidget.container;
      }
    }
    
    class ProviderContainer {
      final Map<ProviderBase, _ProviderState> _states = {};
    
      T read<T>(ProviderBase<T> provider) {
        final state = _states.putIfAbsent(provider, () => provider.createState());
        return state.value;
      }
    }
    
  • 分析:

    • ProviderScope 是一个 InheritedWidget,管理 ProviderContainer。
    • ProviderContainer 存储所有 Provider 的状态(_states),通过 read 访问值。
    • 支持 overrideWith 重写 Provider,适合测试和局部作用域。
  • 用途:定义 Provider 的生命周期和作用域。

(4) ConsumerWidget

  • 源码位置:package:flutter_riverpod/lib/src/consumer.dart

  • 关键代码:

    dart

    abstract class ConsumerWidget extends Widget {
      Widget build(BuildContext context, WidgetRef ref);
    }
    
    class Consumer extends ConsumerWidget {
      Consumer({required this.builder});
    
      final Widget Function(BuildContext, WidgetRef, Widget?) builder;
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        return builder(context, ref, null);
      }
    }
    
  • 分析:

    • ConsumerWidget 通过 WidgetRef 访问 Provider,订阅状态变化。
    • builder 允许局部订阅,减少重绘。
    • WidgetRef 是 Ref 的子类,封装 ProviderContainer 的访问逻辑。

  1. Riverpod 的核心功能与使用

(1) Provider 类型

Riverpod 提供多种 Provider,满足不同场景:

  • Provider:只读值(如配置、服务)。

    dart

    final configProvider = Provider((ref) => AppConfig(apiUrl: 'https://api.example.com'));
    
  • StateProvider:简单的可变状态(不推荐,建议用 StateNotifierProvider)。

    dart

    final counterProvider = StateProvider((ref) => 0);
    
  • StateNotifierProvider:复杂状态管理。

    dart

    final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
    
  • AsyncNotifierProvider:异步状态(如网络请求)。

    dart

    final userProvider = AsyncNotifierProvider<UserNotifier, User>(() => UserNotifier());
    
  • StreamProvider:流式数据(如 WebSocket)。

    dart

    final streamProvider = StreamProvider((ref) => Stream.periodic(Duration(seconds: 1), (i) => i));
    
  • FutureProvider:一次性异步操作。

    dart

    final dataProvider = FutureProvider((ref) => fetchData());
    

(2) 高级功能

  1. 依赖注入:

    • 通过 ref.read 或 ref.watch 访问其他 Provider:

      dart

      final apiProvider = Provider((ref) => ApiService());
      final dataProvider = FutureProvider((ref) {
        final api = ref.read(apiProvider);
        return api.fetchData();
      });
      
  2. Family Provider:

    • 支持参数化 Provider:

      dart

      final userProvider = Provider.family<User, String>((ref, userId) => User(id: userId));
      
  3. Select:

    • 选择性监听状态,减少重绘:

      dart

      final count = ref.watch(counterProvider.select((state) => state.count));
      
  4. AutoDispose:

    • 自动销毁 Provider:

      dart

      final tempProvider = Provider.autoDispose((ref) => TempData());
      
  5. Override:

    • 测试或局部作用域重写:

      dart

      ProviderScope(
        overrides: [configProvider.overrideWithValue(AppConfig(apiUrl: 'test'))],
        child: MyApp(),
      );
      

(3) 生命周期管理

  • ProviderScope:控制 Provider 的存活范围。

  • ref.onDispose:清理资源:

    dart

    final timerProvider = Provider((ref) {
      final timer = Timer.periodic(Duration(seconds: 1), (_) {});
      ref.onDispose(() => timer.cancel());
      return timer;
    });
    

  1. 实践案例:Todo 应用(Riverpod + Clean Architecture)

我们实现一个 Todo 应用,结合 Riverpod 和 Clean Architecture,展示状态管理、异步操作和模块化设计。

(1) 项目结构

lib/
├── data/
│   └── repositories/
│       └── todo_repository.dart
├── domain/
│   ├── entities/
│   │   └── todo.dart
│   ├── repositories/
│   │   └── todo_repository.dart
│   └── usecases/
│       └── add_todo.dart
│       └── get_todos.dart
├── presentation/
│   ├── pages/
│   │   └── todo_page.dart
│   └── providers/
│       └── todo_provider.dart
├── main.dart

(2) 代码实现

Data 层

dart

// lib/data/repositories/todo_repository.dart
import '../../domain/entities/todo.dart';
import '../../domain/repositories/todo_repository.dart';

class TodoRepositoryImpl implements TodoRepository {
  final List<Todo> _todos = [];

  @override
  Future<List<Todo>> getTodos() async {
    await Future.delayed(const Duration(milliseconds: 500)); // 模拟网络
    return _todos;
  }

  @override
  Future<void> addTodo(Todo todo) async {
    await Future.delayed(const Duration(milliseconds: 300));
    _todos.add(todo);
  }
}

Domain 层

dart

// lib/domain/entities/todo.dart
class Todo {
  final String id;
  final String title;
  final bool completed;

  Todo({required this.id, required this.title, this.completed = false});
}

// lib/domain/repositories/todo_repository.dart
import '../entities/todo.dart';

abstract class TodoRepository {
  Future<List<Todo>> getTodos();
  Future<void> addTodo(Todo todo);
}

// lib/domain/usecases/get_todos.dart
import '../entities/todo.dart';
import '../repositories/todo_repository.dart';

class GetTodos {
  final TodoRepository repository;

  GetTodos(this.repository);

  Future<List<Todo>> execute() => repository.getTodos();
}

// lib/domain/usecases/add_todo.dart
import '../entities/todo.dart';
import '../repositories/todo_repository.dart';

class AddTodo {
  final TodoRepository repository;

  AddTodo(this.repository);

  Future<void> execute(Todo todo) => repository.addTodo(todo);
}

Presentation 层

dart

// lib/presentation/providers/todo_provider.dart
import 'package:riverpod/riverpod.dart';
import '../../data/repositories/todo_repository.dart';
import '../../domain/entities/todo.dart';
import '../../domain/usecases/add_todo.dart';
import '../../domain/usecases/get_todos.dart';

final todoRepositoryProvider = Provider((ref) => TodoRepositoryImpl());

final getTodosProvider = Provider((ref) => GetTodos(ref.read(todoRepositoryProvider)));
final addTodoProvider = Provider((ref) => AddTodo(ref.read(todoRepositoryProvider)));

final todoListProvider = AsyncNotifierProvider<TodoListNotifier, List<Todo>>(TodoListNotifier.new);

class TodoListNotifier extends AsyncNotifier<List<Todo>> {
  @override
  Future<List<Todo>> build() async {
    final getTodos = ref.read(getTodosProvider);
    return getTodos.execute();
  }

  Future<void> addTodo(String title) async {
    state = const AsyncLoading();
    try {
      final addTodo = ref.read(addTodoProvider);
      final todo = Todo(id: DateTime.now().toString(), title: title);
      await addTodo.execute(todo);
      state = AsyncData([...state.value ?? [], todo]);
    } catch (e, stack) {
      state = AsyncError(e, stack);
    }
  }
}

dart

// lib/presentation/pages/todo_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/entities/todo.dart';
import '../providers/todo_provider.dart';

class TodoPage extends ConsumerWidget {
  const TodoPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(todoListProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Todo List')),
      body: todos.when(
        data: (todos) => ListView.builder(
          itemCount: todos.length,
          itemBuilder: (context, index) {
            final todo = todos[index];
            return ListTile(
              title: Text(todo.title),
              trailing: Checkbox(
                value: todo.completed,
                onChanged: (_) {}, // 待实现
              ),
            );
          },
        ),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (err, _) => Center(child: Text('Error: $err')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddTodoDialog(context, ref),
        child: const Icon(Icons.add),
      ),
    );
  }

  void _showAddTodoDialog(BuildContext context, WidgetRef ref) {
    final controller = TextEditingController();
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Add Todo'),
        content: TextField(controller: controller),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              ref.read(todoListProvider.notifier).addTodo(controller.text);
              Navigator.pop(context);
            },
            child: const Text('Add'),
          ),
        ],
      ),
    );
  }
}

Main 入口

dart

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'presentation/pages/todo_page.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const TodoPage(),
    );
  }
}

(3) 分析

  1. Clean Architecture:

    • Data 层:TodoRepositoryImpl 模拟网络请求。
    • Domain 层:定义 Todo 实体、TodoRepository 接口和用例(GetTodos、AddTodo)。
    • Presentation 层:TodoPage 处理 UI,todoListProvider 管理状态。
    • 优点:模块化,易于测试和替换实现(如切换到真实 API)。
  2. Riverpod:

    • todoRepositoryProvider:注入存储库。
    • getTodosProvider/addTodoProvider:封装用例。
    • todoListProvider:使用 AsyncNotifierProvider 管理异步状态。
    • ref.watch/ref.read:动态访问和操作状态。
  3. 性能优化:

    • AsyncNotifier 优雅处理加载、成功和错误状态。
    • ListView.builder 延迟构建,优化长列表性能。

(4) 验证

  • 运行 flutter run --profile,添加 Todo 项,检查列表更新。
  • 使用 DevTools 的 Widget Rebuilds 面板,验证仅 ListView 重建。
  • 使用 Performance 面板,分析异步操作的耗时。

  1. 性能优化与高级用法

(1) 优化重绘

  • 问题:ref.watch(todoListProvider) 监听整个列表,任何变化都触发重建。

  • 优化:使用 select:

    dart

    final todoCount = ref.watch(todoListProvider.select((state) => state.value?.length ?? 0));
    Text('Total: $todoCount');
    
  • 效果:仅当列表长度变化时重建。

(2) 异步错误处理

  • 优化:统一错误处理:

    dart

    class TodoListNotifier extends AsyncNotifier<List<Todo>> {
      @override
      Future<List<Todo>> build() async => ref.read(getTodosProvider).execute();
    
      Future<void> addTodo(String title) async {
        state = const AsyncLoading();
        state = await AsyncValue.guard(() async {
          final addTodo = ref.read(addTodoProvider);
          final todo = Todo(id: DateTime.now().toString(), title: title);
          await addTodo.execute(todo);
          return [...state.value ?? [], todo];
        });
      }
    }
    
  • 效果:AsyncValue.guard 自动捕获异常,简化错误处理。

(3) Family Provider

  • 示例:按 ID 获取 Todo:

    dart

    final todoProvider = Provider.family<Todo?, String>((ref, id) {
      final todos = ref.watch(todoListProvider).value ?? [];
      return todos.firstWhere((todo) => todo.id == id, orElse: () => null);
    });
    
    class TodoDetail extends ConsumerWidget {
      final String todoId;
      const TodoDetail(this.todoId, {super.key});
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final todo = ref.watch(todoProvider(todoId));
        return Scaffold(
          appBar: AppBar(title: Text(todo?.title ?? 'Not Found')),
          body: Text(todo?.title ?? ''),
        );
      }
    }
    
  • 效果:动态生成 Provider,适合细节页面。

(4) 测试支持

  • 单元测试:

    dart

    import 'package:flutter_test/flutter_test.dart';
    import 'package:riverpod/riverpod.dart';
    import '../data/repositories/todo_repository.dart';
    import '../domain/usecases/add_todo.dart';
    import '../domain/entities/todo.dart';
    
    void main() {
      test('AddTodo adds todo to repository', () async {
        final container = ProviderContainer();
        final repository = TodoRepositoryImpl();
        final addTodo = AddTodo(repository);
    
        await addTodo.execute(Todo(id: '1', title: 'Test'));
        expect((await repository.getTodos()).length, 1);
      });
    }
    
  • Widget 测试:

    dart

    import 'package:flutter/material.dart';
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    import 'package:flutter_test/flutter_test.dart';
    import '../presentation/pages/todo_page.dart';
    
    void main() {
      testWidgets('TodoPage adds todo', (tester) async {
        await tester.pumpWidget(
          const ProviderScope(child: MaterialApp(home: TodoPage())),
        );
    
        await tester.tap(find.byIcon(Icons.add));
        await tester.pumpAndSettle();
    
        await tester.enterText(find.byType(TextField), 'Test Todo');
        await tester.tap(find.text('Add'));
        await tester.pumpAndSettle();
    
        expect(find.text('Test Todo'), findsOneWidget);
      });
    }
    
  • 效果:Riverpod 的无上下文依赖简化测试,无需模拟 BuildContext。


  1. 总结
  • 核心特点:

    • 无上下文依赖、类型安全、高可测试性。
    • 支持多种 Provider(Provider、StateNotifierProvider、AsyncNotifierProvider)。
  • 源码洞察:

    • ProviderScope 和 ProviderContainer 管理状态生命周期。
    • StateNotifier 和 AsyncNotifier 提供灵活的状态更新。
  • 高级用法:

    • select 优化重绘,family 支持参数化,override 便于测试。
    • AsyncValue.guard 简化异步错误处理。
  • 适用场景:

    • 复杂应用、异步操作、高可测试性需求。
    • 结合 Clean Architecture,构建可扩展项目。