Flutter中使用Hero动画跳转页面后调用pop()返回时出现ProviderNotFoundException的问题排查与解决
我来帮你分析这个问题的原因,以及对应的解决办法:
问题原因
Hero动画的工作机制是在路由切换时,复用同一个Widget实例(这里就是你的GalleryThumbnail),并将它暂时移到Overlay层完成过渡动画。当你从详情页pop返回Gallery页面时,这个GalleryThumbnail在动画完成前,是脱离原来的MultiProvider子树上下文的——而你的GalleryThumbnail内部应该是通过Provider.of<GalleryViewModel>(context)或者Consumer<GalleryViewModel>来获取ViewModel的,这时候它找不到上层的Provider,就会抛出这个错误。
简单说:Hero过渡期间,Widget不在Provider的作用域里了,但它还在尝试获取Provider实例。
解决方法
这里有几个可行的方案,按推荐程度排序:
方案1:给GalleryThumbnail传入所需数据,而非依赖上下文获取Provider
如果GalleryThumbnail需要的所有数据都可以通过构造函数传入(比如你已经传了asset),那直接把它依赖的GalleryViewModel相关数据也作为参数传进去,彻底摆脱对上下文Provider的依赖。
比如,假设GalleryThumbnail原本需要从GalleryViewModel获取选中状态,那改成:
class GalleryThumbnail extends StatelessWidget { final Asset asset; final bool isSelected; // 直接传入需要的数据 final Function(Uint8List) onTap; const GalleryThumbnail({ super.key, required this.asset, required this.isSelected, required this.onTap, }); @override Widget build(BuildContext context) { // 内部直接使用isSelected,不再调用Provider.of return /* ... */; } }
然后在_galleryItem中传入:
GalleryThumbnail( asset: galleryViewModel.assetsList[index], isSelected: galleryViewModel.isSelected(index), // 从ViewModel获取后传入 onTap: (data) { /* ... */ }, )
这个方案最彻底,也符合Widget的设计原则——让Widget只关注UI,不依赖外部状态管理上下文。
方案2:在Hero内部嵌套局部Provider
如果你不想修改GalleryThumbnail的代码,可以在Hero的child外面包裹一个局部的Provider.value,把galleryViewModel传进去,这样即使在Overlay层,GalleryThumbnail也能通过这个局部Provider获取到实例:
Hero( tag: galleryViewModel.assetsList[index].id, createRectTween: (Rect? begin, Rect? end) => RectTween(begin: begin, end: end), child: Provider<GalleryViewModel>.value( value: galleryViewModel, child: GalleryThumbnail( asset: galleryViewModel.assetsList[index], onTap: (data) { /* ... */ }, ), ), )
这个方案不需要改动GalleryThumbnail,只需要在Hero层做包裹,适合快速修复问题。
方案3:修改Hero的过渡行为(不推荐)
你也可以尝试禁用Hero在返回时的动画,但这会牺牲用户体验,所以不推荐。比如在push路由时设置HeroControllerScope.none:
Navigator.push( context, HeroControllerScope.none( child: MaterialPageRoute(builder: (context) => MediaEditorPage(...)), ), );
但这样就失去了Hero动画的意义,所以只作为最后的备选。
验证建议
优先尝试方案1,它能让你的代码结构更清晰;如果时间紧张,方案2可以快速解决问题。测试时注意返回时的动画过程,确保错误不再出现。
内容的提问来源于stack exchange,提问作者nicover

