riverpod常用api与最佳实践

62 阅读2分钟

Riverpod

作用:Flutter 中更现代、更类型安全的响应式状态管理库。

  • ProviderScope

    根节点用ProviderScope包裹

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

provider相关

  • provider

Provider 适合放 只读依赖 或 计算值。

/// 声明
final apiClientProvider = Provider<ApiClient>((ref) {  
return ApiClient();  
});

/// 使用
final apiClient = ref.watch(apiClientProvider);
  • StateProvider

适合非常简单的局部状态,比如开关、计数器、Tab 下标、输入框临时值。

final selectedTabProvider = StateProvider<int>((ref) => 0);  
final isDarkModeProvider = StateProvider<bool>((ref) => false);  
final keywordProvider = StateProvider<String>((ref) => '');
  • FutureProvider

FutureProvider 适合 只读异步数据,比如页面初始化时请求接口。

通常用于:

  • 获取用户信息
  • 获取配置
  • 获取列表等
/// 声明
final userProvider = FutureProvider<User>((ref) async {
  final repository = ref.watch(userRepositoryProvider);
  return repository.getUser();
});

/// 使用
final userAsync = ref.watch(userProvider);

return userAsync.when(
  data: (user) => Text(user.name),
  loading: () => const CircularProgressIndicator(),
  error: (error, stack) => Text('加载失败: $error'),
);
  • StreamProvider

StreamProvider 适合监听持续变化的数据。

适用于:

  • WebSocket
  • 本地数据库监听
  • 实时消息
  • 倒计时
final messageStreamProvider = StreamProvider<List<Message>>((ref) {
  final repository = ref.watch(messageRepositoryProvider);
  return repository.watchMessages();
});

NotifierProvider&AsyncNotifierProvider&StateNotifierProvider

  • NotifierProvider

适合管理同步业务状态,比如购物车、本地筛选条件、主题、表单状态、登录态缓存等。

/// 定义
final themeControllerProvider =
    NotifierProvider<ThemeController, ThemeMode>(ThemeController.new);

class ThemeController extends Notifier<ThemeMode> {
  @override
  ThemeMode build() {
    return ThemeMode.system;
  }

  void setThemeMode(ThemeMode mode) {
    state = mode;
  }

  void toggleDarkMode() {
    state = state == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
  }
}

/// 使用
final themeMode = ref.watch(themeControllerProvider);

MaterialApp(
  themeMode: themeMode,
  theme: ThemeData.light(),
  darkTheme: ThemeData.dark(),
);

ref.read(themeControllerProvider.notifier).toggleDarkMode();
  • AsyncNotifierProvider

用户列表、房间列表、商品列表、添加、删除、更新、刷新等任何与后台相关的数据

/// 声明
final userListProvider =  
    AsyncNotifierProvider<UserListController, List<User>>(  
    UserListController.new,  
);  
  
class UserListController extends AsyncNotifier<List<User>> {  
    late final UserRepository _repository;  

    @override  
    Future<List<User>> build() async {  
    _repository = ref.watch(userRepositoryProvider);  
    return _repository.fetchUsers();  
    }  

    Future<void> refresh() async {  
    state = const AsyncLoading();  

    state = await AsyncValue.guard(() async {  
    return _repository.fetchUsers();  
    });  
    }  

    Future<void> deleteUser(String userId) async {  
    await _repository.deleteUser(userId);  

    final currentUsers = state.valueOrNull ?? [];  

    state = AsyncData(  
    currentUsers.where((user) => user.id != userId).toList(),  
    );  
    }  
}

/// 使用
final usersAsync = ref.watch(userListProvider);  
  
return usersAsync.when(  
    data: (users) {  
    return ListView.builder(  
        itemCount: users.length,  
        itemBuilder: (_, index) {  
        final user = users[index];  

        return ListTile(  
        title: Text(user.name),  
        trailing: IconButton(  
        icon: const Icon(Icons.delete),  
        onPressed: () {  
        ref.read(userListProvider.notifier).deleteUser(user.id);  
        },  
        ),  
        );  
        },  
        );  
    },  
    loading: () => const CircularProgressIndicator(),  
    error: (error, stack) => Text('加载失败: $error'),  
);
  • StateNotifierProvider

老版本的写法,将状态与模型一起管理,在内部实现状态的更新。

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

class UserNotifier extends StateNotifier<AsyncValue<User>> {
  UserNotifier(this._repository) : super(const AsyncLoading()) {
    load();
  }

  final UserRepository _repository;

  Future<void> load() async {
    state = await AsyncValue.guard(() {
      return _repository.getUser();
    });
  }
}

ConsumerWidget & ConsumerStatefulWidget

  • ConsumerWidget

适合无本地生命周期逻辑的页面。

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

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

    return usersAsync.when(
      data: (users) => Text('用户数: ${users.length}'),
      loading: () => const CircularProgressIndicator(),
      error: (error, stack) => Text('错误: $error'),
    );
  }
}
  • ConsumerStatefulWidget

适合需要 initState、dispose、TabController、AnimationController 的页面。

class UserPage extends ConsumerStatefulWidget {
  const UserPage({super.key});

  @override
  ConsumerState<UserPage> createState() => _UserPageState();
}

class _UserPageState extends ConsumerState<UserPage> {
  @override
  void initState() {
    super.initState();

    Future.microtask(() {
      ref.read(userListProvider.notifier).refresh();
    });
  }

  @override
  Widget build(BuildContext context) {
    final usersAsync = ref.watch(userListProvider);

    return usersAsync.when(
      data: (users) => Text('用户数: ${users.length}'),
      loading: () => const CircularProgressIndicator(),
      error: (error, stack) => Text('错误: $error'),
    );
  }
}

ref.watch / ref.read / ref.listen

1. ref.watch

用于 监听状态变化并重建 UI。

final count = ref.watch(counterProvider);

适合写在 build 中。

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

  return Text('$count');
}

只要 counterProvider 变化,当前 Widget 会重新 build。


2. ref.read

用于 读取一次状态,不监听变化。

常用于按钮点击、事件回调。

onPressed: () {
  ref.read(counterProvider.notifier).state++;
}

不建议在 build 中大量使用 read 来获取 UI 数据,因为它不会自动刷新页面。


3. ref.listen

用于 监听状态变化并执行副作用。

比如弹 Toast、跳转页面、显示 Dialog。

ref.listen(authControllerProvider, (previous, next) {
  if (next.hasError) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('登录失败')),
    );
  }
});

family:带参数的 Provider

family 用于根据参数创建不同的 Provider 实例。根据筛选条件查询:

/// 声明
final userDetailProvider = FutureProvider.family<User, String>((ref, userId) {
  final repository = ref.watch(userRepositoryProvider);
  return repository.getUserDetail(userId);
});

/// 使用
final userAsync = ref.watch(userDetailProvider(userId));

autoDispose / keepAlive

autoDispose 表示没有页面监听时,Provider 会自动释放。

在新版代码生成方式中,默认经常是自动释放;需要长期缓存时可以配置 keepAlive。

适合自动释放:

场景
详情页数据
搜索结果
临时筛选条件
页面级状态

适合 keepAlive:

场景
登录用户信息
全局配置
主题设置
Token 管理
App 初始化状态

使用注解时:

@Riverpod(keepAlive: true)
class AuthController extends _$AuthController {
  @override
  Future<User?> build() async {
    return getCurrentUser();
  }
}

代码生成方式:@riverpod

flutter pub add flutter_riverpod riverpod_annotation
flutter pub add dev:build_runner dev:riverpod_generator

添加了注解后的写法可以变成

@riverpod
class UserListController extends _$UserListController {
  @override
  Future<List<User>> build() async {
    final repository = ref.watch(userRepositoryProvider);
    return repository.fetchUsers();
  }

  Future<void> refresh() async {
    final repository = ref.read(userRepositoryProvider);

    state = const AsyncLoading();

    state = await AsyncValue.guard(() async {
      return repository.fetchUsers();
    });
  }

  Future<void> deleteUser(String userId) async {
    final repository = ref.read(userRepositoryProvider);

    final previousUsers = state.valueOrNull ?? [];

    state = AsyncData(
      previousUsers.where((user) => user.id != userId).toList(),
    );

    try {
      await repository.deleteUser(userId);
    } catch (error, stackTrace) {
      state = AsyncError(error, stackTrace);
    }
  }
}