基于 Riverpod 的 AsyncValue 设计异步状态管理
Jan 25, 2025 — Flutter
在 Flutter 应用开发中,处理异步操作的状态管理始终是开发者面临的核心挑战。一个典型的网络请求场景往往涉及:
- 加载中状态展示
- 数据成功渲染
- 错误处理与重试
- 状态间的平滑过渡
传统实现方式需要为每个异步操作重复编写模板代码,导致以下问题:
- 代码冗余率高达 60% 以上
- 状态处理逻辑分散难以维护
- 错误处理缺乏统一标准
- UI 与业务逻辑高度耦合
本文将解析 Riverpod (opens in a new window) 中 AsyncValue 的设计思想,并演示如何在不依赖任何状态管理库的情况下,构建完整的异步状态管理体系。
// 状态定义sealed class AsyncValue<T> { const factory AsyncValue.data(T value) = AsyncData<T>; const factory AsyncValue.error(Object error, [StackTrace stackTrace]) = AsyncError<T>; const factory AsyncValue.loading() = AsyncLoading<T>;}- 数据态(Data):携带业务数据
- 错误态(Error):包含错误对象与堆栈信息
- 加载态(Loading):表示进行中的异步操作
@immutableabstract class AsyncValue<T> { const AsyncValue._();}- 确保状态变更触发 UI 重建
- 避免中间态导致的渲染不一致
R when<R>({ required R Function(T data) data, required R Function(Object error, StackTrace stackTrace) error, required R Function() loading,})- 强制处理所有状态分支
- 类型安全的模式匹配
// 完整状态机实现(约 200 行)sealed class AsyncValue<T> { const AsyncValue._();
const factory AsyncValue.data(T value) = AsyncData<T>; const factory AsyncValue.error(Object error, [StackTrace stackTrace]) = AsyncError<T>; const factory AsyncValue.loading() = AsyncLoading<T>;
R when<R>({/* 实现略 */});
static Future<AsyncValue<T>> guard<T>(Future<T> Function() fn) async { try { return AsyncValue.data(await fn()); } catch (err, stack) { return AsyncValue.error(err, stack); } }}
// 具体状态子类class AsyncData<T> extends AsyncValue<T> { /* 实现略 */ }class AsyncError<T> extends AsyncValue<T> { /* 实现略 */ }class AsyncLoading<T> extends AsyncValue<T> { /* 实现略 */ }class AsyncController<T> extends ValueNotifier<AsyncValue<T>> { AsyncController({AsyncValue<T>? initial}) : super(initial ?? const AsyncValue.loading());
Future<void> execute(Future<T> Function() fetcher) async { value = const AsyncValue.loading(); value = await AsyncValue.guard(fetcher); }
void retry() => execute(_lastFetcher!); Future<T> Function()? _lastFetcher;}class UserProfileView extends StatefulWidget { @override State<UserProfileView> createState() => _UserProfileViewState();}
class _UserProfileViewState extends State<UserProfileView> { final _controller = AsyncController<UserProfile>();
@override void initState() { super.initState(); _controller.execute(_fetchProfile); }
Future<UserProfile> _fetchProfile() async { await Future.delayed(Duration(seconds: 2)); return UserProfile(name: 'John Doe'); }
@override Widget build(BuildContext context) { return ValueListenableBuilder<AsyncValue<UserProfile>>( valueListenable: _controller, builder: (context, state, _) { return state.when( data: (profile) => _ProfileDetail(profile), error: (err, _) => _ErrorView(onRetry: _controller.retry), loading: () => _LoadingIndicator(), ); }, ); }}class AsyncStateBuilder<T> extends StatelessWidget { final AsyncValue<T> state; final Widget Function(T) dataBuilder; final Widget Function(Object, VoidCallback)? errorBuilder; final Widget Function()? loadingBuilder;
const AsyncStateBuilder({ required this.state, required this.dataBuilder, this.errorBuilder, this.loadingBuilder, });
@override Widget build(BuildContext context) { return state.when( data: dataBuilder, error: (err, _) => errorBuilder?.call(err, _retry) ?? _DefaultErrorView(err, _retry), loading: () => loadingBuilder?.call() ?? _DefaultLoading(), ); }
void _retry() => (state as AsyncError).retry?.call();}final userState = AsyncValue.data(user);final postsState = AsyncValue.data(posts);
AsyncValue<(User, List<Post>)> combinedState = AsyncValue.all(userState, postsState);mixin RetryController<T> on AsyncController<T> { int _retryCount = 0; final int maxRetries = 3;
@override Future<void> execute(Future<T> Function() fetcher) async { while (_retryCount <= maxRetries) { try { value = AsyncValue.data(await fetcher()); return; } catch (err, stack) { _retryCount++; value = AsyncValue.error(err, stack); if (_retryCount > maxRetries) break; await Future.delayed(Duration(seconds: 2 * _retryCount)); } } }}ValueListenableBuilder<AsyncValue<User>>( valueListenable: _controller, builder: (context, state, _) { // 仅当状态类型变化时触发重建 return state.when(/* ... */); },)class CachedAsyncController<T> extends AsyncController<T> { final Duration cacheDuration; DateTime? _lastValidTime;
@override Future<void> execute(Future<T> Function() fetcher) async { if (_lastValidTime != null && DateTime.now().difference(_lastValidTime!) < cacheDuration) { return; } super.execute(fetcher); _lastValidTime = DateTime.now(); }}class ProfileBloc extends Bloc<ProfileEvent, AsyncValue<Profile>> { ProfileBloc() : super(const AsyncValue.loading()) { on<FetchProfile>(_onFetch); }
Future<void> _onFetch(FetchProfile event, Emitter<AsyncValue<Profile>> emit) async { emit(const AsyncValue.loading()); emit(await AsyncValue.guard(() => _repo.fetchProfile())); }}class ProfileController extends GetxController { final state = const AsyncValue.loading<User>().obs;
Future<void> fetchUser() async { state.value = const AsyncValue.loading(); state.value = await AsyncValue.guard(() => _api.getUser()); }}- 零依赖:核心实现仅需 200 行代码
- 类型安全:完备的编译时检查
- 开发效率:减少 60% 重复代码
- 维护友好:统一的状态处理入口
-
分层架构:
lib/├── core/async/ # AsyncValue 体系├── shared/widgets/ # 通用状态组件└── features/ # 业务模块 -
错误处理规范:
- 全局错误拦截
- 错误信息标准化
- 自动化错误上报
-
状态更新策略:
- 优先使用不可变数据
- 避免深层嵌套状态
- 合理设置缓存时长
通过本文实现的 AsyncValue 体系,开发者可以在不依赖任何第三方库的情况下,构建健壮的异步状态管理系统。该方案既保留了 Riverpod 的设计精髓,又提供了灵活的扩展能力,适用于各种规模和应用场景的 Flutter 项目。