Flutter用intro_slider做手册后切换导航栏触发State异常排查
切换底部导航栏时IntroSlider组件触发"已卸载组件使用上下文"异常
我用intro_slider在Flutter应用内制作用户手册页面,将手册组件ManageRecipe放在Profile页面中,用户登录后可查看。但通过底部导航栏切换各模块时,触发了FlutterError异常:
This widget has been unmounted, so the State no longer has a context (and should be considered defunct). Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
相关代码
manageRecipe.dart
import 'package:intro_slider/intro_slider.dart'; class ManageRecipe extends StatefulWidget { @override State<ManageRecipe> createState() => _ManageRecipeState(); } class _ManageRecipeState extends State<ManageRecipe> { List<Slide> listSlides = []; @override Widget build(BuildContext context) { return IntroSlider( slides: listSlides, onDonePress: onPressedDone, ); } @override void initState() { super.initState(); listSlides.add(Slide( title: "Premier League", description: "English Premier League or EPL", pathImage: "assets/userManual/userManual_background.png", backgroundColor: Colors.pinkAccent, )); listSlides.add(Slide( title: "La Liga", description: "The Campeonato Nacional de Liga de Primera División", pathImage: "assets/userManual/userManual_background.png", backgroundColor: Colors.deepOrangeAccent, )); listSlides.add(Slide( title: "Bundesliga", description: "Federal League", pathImage: "assets/userManual/userManual_background.png", backgroundColor: Colors.redAccent, )); listSlides.add(Slide( title: "Serie A", description: "Lega Nazionale Professionisti Serie A", pathImage: "assets/userManual/userManual_background.png", backgroundColor: Colors.blueAccent, )); listSlides.add(Slide( title: "Ligue 1", description: "Ligue 1 Conforama", pathImage: "assets/userManual/userManual_background.png", backgroundColor: Colors.amberAccent, )); } onPressedDone() {} }
navigationBar.dart
import 'package:convex_bottom_bar/convex_bottom_bar.dart'; import 'package:recipe_collector/All%20Recipes/viewRecipe.dart'; import 'package:recipe_collector/OCR/OCR_Page.dart'; import 'package:recipe_collector/Profile/Profile.dart'; import 'package:recipe_collector/Recipes%20Collection/RecipeSearch.dart'; import 'package:recipe_collector/Recipes%20Collection/recipe_collection_home.dart'; import 'package:recipe_collector/BottomNavigationBar.dart'; import 'BuyingList/buyingHome.dart'; class BottomNav extends StatefulWidget { const BottomNav({super.key}); @override State<BottomNav> createState() => _BottomNavState(); } class _BottomNavState extends State<BottomNav> { int _currentindex = 2; List pages = [ RecipesViewPage(), RecipeCollectionHomePage(), OCRPage(), HomeBuying(), Profile() ]; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: pages[_currentindex], bottomNavigationBar: ConvexAppBar( style: TabStyle.reactCircle, backgroundColor: Color.fromARGB(255, 51, 44, 25), items: [ TabItem(icon: Icons.menu_book_rounded, title: 'All Recipes'), TabItem(icon: Icons.folder_copy_rounded, title: 'Collection'), TabItem(icon: Icons.document_scanner_rounded, title: 'OCR'), TabItem(icon: Icons.checklist_outlined, title: 'Buying List'), TabItem(icon: Icons.perm_contact_cal, title: 'Profile'), ], initialActiveIndex: 2, //optional, default as 0 onTap: (int i) { if (mounted) { setState(() { _currentindex = i; }); } })), ); } }
问题根源
- 页面实例提前初始化引发状态冲突:
pages列表直接创建了所有页面的实例(包括包含ManageRecipe的Profile),切换导航栏时,这些实例会被卸载再重新挂载,而IntroSlider内部可能持有已卸载组件的上下文引用,或者在组件卸载后仍有异步操作(比如图片加载)尝试更新状态,导致异常。 - IntroSlider内部资源未及时清理:该组件在页面卸载后,可能存在未终止的回调或状态更新逻辑,试图访问已失效的
context。
修复方案
方案1:延迟创建页面实例(推荐)
把pages列表改为返回Widget的函数列表,每次切换导航时才创建新的页面实例,避免复用已卸载的组件:
class _BottomNavState extends State<BottomNav> { int _currentindex = 2; // 改为函数列表,每次调用才生成页面实例 List<Widget Function()> pages = [ () => RecipesViewPage(), () => RecipeCollectionHomePage(), () => OCRPage(), () => HomeBuying(), () => Profile() ]; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( // 调用函数创建当前页面 body: pages[_currentindex](), bottomNavigationBar: ConvexAppBar( style: TabStyle.reactCircle, backgroundColor: Color.fromARGB(255, 51, 44, 25), items: [ TabItem(icon: Icons.menu_book_rounded, title: 'All Recipes'), TabItem(icon: Icons.folder_copy_rounded, title: 'Collection'), TabItem(icon: Icons.document_scanner_rounded, title: 'OCR'), TabItem(icon: Icons.checklist_outlined, title: 'Buying List'), TabItem(icon: Icons.perm_contact_cal, title: 'Profile'), ], initialActiveIndex: 2, onTap: (int i) { if (mounted) { setState(() { _currentindex = i; }); } })), ); } }
方案2:手动清理IntroSlider资源
如果需要保留页面实例复用,在_ManageRecipeState中重写dispose方法,清理IntroSlider的内部资源:
class _ManageRecipeState extends State<ManageRecipe> { List<Slide> listSlides = []; IntroSliderController? _sliderController; @override void initState() { super.initState(); _sliderController = IntroSliderController(); // 原有初始化slide的代码不变 listSlides.add(Slide(...)); // ... 其他slide添加逻辑 } @override void dispose() { // 销毁控制器,终止内部异步操作 _sliderController?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return IntroSlider( controller: _sliderController, slides: listSlides, onDonePress: onPressedDone, ); } onPressedDone() {} }
方案3:保留页面状态(可选)
如果希望Profile页面切换后保留状态,使用AutomaticKeepAliveClientMixin,避免页面被频繁卸载:
class Profile extends StatefulWidget { @override State<Profile> createState() => _ProfileState(); } class _ProfileState extends State<Profile> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @override Widget build(BuildContext context) { super.build(context); // 必须调用此方法 return ManageRecipe(); } }
内容的提问来源于stack exchange,提问作者user19402977
相关产品推荐
相关产品推荐

