使用Riverpod监听Image.network加载状态并存入state的最优方案是什么?
Flutter 监听Image.network加载状态并存入Riverpod的最优实现
原有实现的核心问题
- 仅覆盖加载成功场景:
loadingBuilder的loadingProgress == null仅代表加载完成且成功,加载失败、资源无效、网络错误等场景完全没有处理,状态会一直停留在初始值,不符合业务预期 - 存在build阶段改状态的风险:在widget的build参数中直接修改Riverpod状态,属于build执行过程中触发状态更新,非常容易触发
setState during build异常,导致页面闪退或者无限重绘 - 状态修改方式不规范:Riverpod 1.0之后
StateProvider的状态修改需要通过.notifier访问控制器修改,直接读provider修改状态的写法已经废弃,会导致编译警告或者运行异常
最优实现方案
核心采用Flutter官方提供的ImageStreamListener监听图片加载全周期状态,所有状态更新放到帧回调中执行,规避build阶段改状态的问题。
第一步:Provider定义
Riverpod 2.x 推荐写法(更易维护)
final validPhotoProvider = NotifierProvider<ValidPhotoNotifier, bool>(ValidPhotoNotifier.new); class ValidPhotoNotifier extends Notifier<bool> { @override bool build() => false; void onLoadSuccess() => state = true; void onLoadFailed() => state = false; }
Riverpod 1.x 兼容写法
final validPhotoProvider = StateProvider<bool>((ref) => false);
第二步:图片加载及状态监听实现
ConsumerStatefulBuilder( builder: (context, ref, child) { final imageProvider = NetworkImage(photoUrl); // 注册全周期加载监听 imageProvider.resolve(const ImageConfiguration()).addListener( ImageStreamListener( // 加载成功回调 (image, synchronousCall) { WidgetsBinding.instance.addPostFrameCallback((_) { // 2.x版本写法 ref.read(validPhotoProvider.notifier).onLoadSuccess(); // 1.x版本写法 // ref.read(validPhotoProvider.notifier).state = true; }); }, // 加载失败回调 onError: (error, stackTrace) { WidgetsBinding.instance.addPostFrameCallback((_) { // 2.x版本写法 ref.read(validPhotoProvider.notifier).onLoadFailed(); // 1.x版本写法 // ref.read(validPhotoProvider.notifier).state = false; }); }, ), ); return Image( image: imageProvider, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return const CircularProgressIndicator(); }, errorBuilder: (context, error, stackTrace) { // 自定义加载失败占位UI return const Icon(Icons.broken_image, size: 48); }, ); }, )
方案优势
- 全场景覆盖:加载成功、加载失败都有对应回调,状态不会出现遗漏
- 无异常风险:所有状态更新都放在
addPostFrameCallback中执行,完全规避build过程中修改状态的异常 - 逻辑可复用:监听逻辑和UI渲染分离,后续可以封装成通用组件直接使用
- 状态更新精确:仅当加载状态真实变化时才会更新Riverpod状态,不会出现重复触发无意义rebuild的问题
可选优化点
- 内存泄漏防护:将
ImageStreamListener实例存为变量,在页面/组件销毁时调用removeListener移除监听,避免内存泄漏 - 多图片场景适配:如果需要同时监听多张图片的加载状态,可以给Provider加
family修饰符,传入图片url作为唯一标识区分不同图片的状态
内容的提问来源于stack exchange,提问作者ejandra
相关产品推荐
相关产品推荐

