跳转到正文
YGJoon 技术博客

Flutter Riverpod 状态管理详解

发布于:
·1.1k 字 · 约 3 分钟·作者:
Provider 的继任者:编译时安全、不依赖 BuildContext 取数,FutureProvider 直接处理异步,ref.watch 订阅更新
Flutter Riverpod 状态管理详解文章封面
目录

简介

Riverpod 是 Provider 的重新设计版本,它提供了更安全的依赖管理和更好的代码组织方式。Riverpod 完全支持编译时安全,并且可以轻松处理异步数据。

基本概念

Provider

// 基础 Provider
final nameProvider = Provider<String>((ref) => 'John Doe');

// 状态 Provider
final counterProvider = StateProvider<int>((ref) => 0);

// 异步 Provider
final userProvider = FutureProvider<User>((ref) async {
  final repository = ref.read(repositoryProvider);
  return repository.fetchUser();
});

StateNotifier

class Counter extends StateNotifier<int> {
  Counter() : super(0);
  
  void increment() => state++;
  void decrement() => state--;
}

final counterProvider = StateNotifierProvider<Counter, int>((ref) {
  return Counter();
});

基本用法

提供状态

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

使用状态

// 读取状态
Consumer(
  builder: (context, ref, child) {
    final count = ref.watch(counterProvider);
    return Text('Count: $count');
  },
);

// 修改状态
ref.read(counterProvider.notifier).increment();

完整示例

// 用户模型
class User {
  final String id;
  final String name;
  final int age;
  
  User({
    required this.id,
    required this.name,
    required this.age,
  });
}

// 用户状态
class UserState {
  final User? user;
  final bool isLoading;
  final String? error;
  
  UserState({
    this.user,
    this.isLoading = false,
    this.error,
  });
  
  UserState copyWith({
    User? user,
    bool? isLoading,
    String? error,
  }) {
    return UserState(
      user: user ?? this.user,
      isLoading: isLoading ?? this.isLoading,
      error: error ?? this.error,
    );
  }
}

// 用户 Notifier
class UserNotifier extends StateNotifier<UserState> {
  final UserRepository _repository;
  
  UserNotifier(this._repository) : super(UserState());
  
  Future<void> fetchUser() async {
    state = state.copyWith(isLoading: true, error: null);
    
    try {
      final user = await _repository.fetchUser();
      state = state.copyWith(
        user: user,
        isLoading: false,
      );
    } catch (e) {
      state = state.copyWith(
        error: e.toString(),
        isLoading: false,
      );
    }
  }
  
  Future<void> updateUser(String name, int age) async {
    state = state.copyWith(isLoading: true, error: null);
    
    try {
      final updatedUser = await _repository.updateUser(
        state.user!.id,
        name,
        age,
      );
      state = state.copyWith(
        user: updatedUser,
        isLoading: false,
      );
    } catch (e) {
      state = state.copyWith(
        error: e.toString(),
        isLoading: false,
      );
    }
  }
}

// Provider 定义
final userRepositoryProvider = Provider<UserRepository>((ref) {
  return UserRepository();
});

final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
  final repository = ref.watch(userRepositoryProvider);
  return UserNotifier(repository);
});

// 页面组件
class UserPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userState = ref.watch(userProvider);
    
    return Scaffold(
      appBar: AppBar(title: Text('Riverpod Demo')),
      body: Center(
        child: userState.isLoading
            ? CircularProgressIndicator()
            : userState.error != null
                ? Text('Error: ${userState.error}')
                : userState.user == null
                    ? ElevatedButton(
                        onPressed: () {
                          ref.read(userProvider.notifier).fetchUser();
                        },
                        child: Text('Load User'),
                      )
                    : Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Text(
                            'Name: ${userState.user!.name}',
                            style: TextStyle(fontSize: 24),
                          ),
                          Text(
                            'Age: ${userState.user!.age}',
                            style: TextStyle(fontSize: 24),
                          ),
                          ElevatedButton(
                            onPressed: () {
                              ref.read(userProvider.notifier).updateUser(
                                'John Doe',
                                30,
                              );
                            },
                            child: Text('Update User'),
                          ),
                        ],
                      ),
      ),
    );
  }
}

高级用法

组合 Provider

final userSettingsProvider = Provider<Settings>((ref) {
  final user = ref.watch(userProvider);
  return Settings(userId: user.id);
});

自动释放

final cacheProvider = Provider.autoDispose<Cache>((ref) {
  final cache = Cache();
  ref.onDispose(() => cache.dispose());
  return cache;
});

状态保持

final counterProvider = StateProvider.autoDispose<int>((ref) {
  ref.maintainState = true;
  return 0;
});

最佳实践

  1. 页面级 provider 加 autoDispose:FutureProvider.autoDispose((ref) => fetchDetail(id))——页面 pop 后自动释放;不加的话,每个进过的详情页数据都常驻内存(和 GetX 篇”退出登录 controller 没清”是同类坑)
  2. 用 family 传参:detailProvider(id) 按 id 区分实例;注意 family 的参数必须不可变且 == 稳定——传可变对象会导致缓存 miss,每次都重复请求
  3. build 里 ref.watch、回调里 ref.read:和 Provider 的 watch/read 同理,watch 放错地方(比如 onPressed 里)会抛异常
  4. 测试用 override:ProviderContainer(overrides: [apiProvider.overrideWithValue(mock)])——这是 Riverpod 比 GetX 强的地方,单测不用起整个 App

注意事项

  1. 别在 provider 里写出循环依赖:A watch B、B watch A,运行时直接抛 CircularDependencyError,编译器可不会提前告诉你
  2. 状态必须是不可变的:state = [...state, item] 而不是 state.add(item)——直接改原对象,监听者收不到通知(和 GetX 的 list.add 同一个坑)
  3. 全局用户态别 autoDispose:每次 watch 重建会闪一下 loading;常驻的状态用 ref.keepAlive() 或干脆不用 autoDispose
  4. family 别滥用:userId+tab+filter 三个 family 嵌套,参数组合爆炸;考虑把参数包成一个不可变对象,或退回普通 provider 内部 switch

实战案例

Riverpod 我研究过,没在生产项目用。它的 ref.watch 编译期安全、测试友好,设计上比 Provider 先进。但我的五个 App 已经是 GetX 技术栈(get: ^4.7.2 锁死),切 Riverpod 意味着重写所有 controller/view,收益撑不起成本。而且 Riverpod 的”正确”用法(family、autoDispose 的各种组合)学习曲线并不低,网上的最佳实践还在演进中。我的判断是:新项目可以考虑 Riverpod,老项目别折腾。技术选型里”迁移成本”是经常被低估的一项,重写一时爽,联调火葬场。

总结

Riverpod 提供了一种类型安全、可测试的状态管理方案。通过合理使用 Provider 和 StateNotifier,可以构建出可维护性更好的应用程序。理解并掌握 Riverpod 的使用对于开发高质量的 Flutter 应用至关重要。


分享这篇文章:


相关文章

上一篇
Flutter Redux 状态管理详解
下一篇
Flutter 渲染优化详解