Flutter:仅在首页根据网络状态显示SnackBar的问题排查
Flutter 网络状态SnackBar仅在首页显示问题
需求
我有一个Flutter首页内容组件,希望用户失去网络连接时显示SnackBar,但部分支持离线访问的页面不需要显示这个SnackBar。
当前实现
在首页组件中使用Consumer监听ConnectivityProvider,通过助手函数控制SnackBar的显示。
问题
尽管只在首页添加了Consumer,但SnackBar仍会在所有页面弹出。排查发现助手函数toggleConnectivitySnackbar中获取的路由名称始终为"/",即使切换到其他页面也是如此。尝试使用全局Key和传递首页Context均未解决问题。
首页组件代码
class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); @override void initState() { super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { final BuildContext homeContext = context; return Scaffold( body: SingleChildScrollView( child: Container( padding: const EdgeInsets.all(20), child: Consumer<ConnectivityProvider>( builder: (context, connectivityProvider, _) { return Column( children: [ FutureBuilder<Map<String, dynamic>>( future: fetchHomeScreenData(Localizations.localeOf(context).toString()), builder: (BuildContext context, AsyncSnapshot snapshot) { AppHelper.toggleConnectivitySnackbar(homeContext, connectivityProvider, _); final result = AppHelper.apiResponseHandler(context, snapshot); if (result != null) { return result; } return Column(children: [ Consumer<FirebaseSubscriptionProvider>( builder: (context, firebaseSubscriptionProvider, _){ firebaseSubscriptionProvider.loadSubscriptionTopics(); final isSubscribed = firebaseSubscriptionProvider.isTopicSubscribed('new_post') ; final subscriptionColor = isSubscribed ? AppColors.orange : AppColors.green; return Container(); } ), ]); }, ), ], ); } ), ), ), ); } }
助手函数代码
static void toggleConnectivitySnackbar(BuildContext context, ConnectivityProvider connectivityProvider) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!connectivityProvider.isConnected) { print("Helper Showing Snackbar on ${ModalRoute.of(context)?.settings.name}"); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(AppLocalizations.of(context)!.no_internet_connection), duration: const Duration(days: 1), backgroundColor: Theme.of(context).colorScheme.error, ), ); } else{ ScaffoldMessenger.of(context).clearSnackBars(); } }); }
路由生成器代码
class AppRouter { static Route onGenerateRoute(RouteSettings settings, BuildContext? context) { switch (settings.name) { case '/': return AppMainScreen.route(); case SelectLanguageScreen.routeName: return SelectLanguageScreen.route(); default: return errorRoute(); } } static Route errorRoute() { return MaterialPageRoute( settings: const RouteSettings(name: '/error'), builder: (_){ return const Scaffold( appBar: AppMainAppbar(), body: Center( child: Icon(Icons.warning_outlined, size: 100.0, color: Colors.red), ), ); }, ); } }
问题原因
- 首页的
Consumer<ConnectivityProvider>在页面切换后未被销毁(首页仍保留在导航栈中),网络状态变化时仍会触发builder回调。 - 传递的
homeContext是首页的上下文,对应的路由始终为"/",导致路由判断逻辑失效,SnackBar被全局触发。
解决办法
方案1:手动订阅网络状态,页面销毁时取消订阅
修改首页组件,在initState中手动订阅网络状态,dispose时取消订阅,确保页面出栈后不再响应网络变化:
class _HomeScreenState extends State<HomeScreen> { late StreamSubscription _connectivitySubscription; @override void initState() { super.initState(); // 订阅网络状态流 final connectivityProvider = ConnectivityProvider.of(context, listen: false); _connectivitySubscription = connectivityProvider.connectivityStream.listen((isConnected) { if (mounted && ModalRoute.of(context)?.isCurrent == true) { // 仅当前页面处于栈顶时处理 AppHelper.toggleConnectivitySnackbar(context, isConnected); } }); } @override void dispose() { _connectivitySubscription.cancel(); // 取消订阅 super.dispose(); } // 移除原build方法中的Consumer<ConnectivityProvider>,避免重复监听 @override Widget build(BuildContext context) { return Scaffold( body: SingleChildScrollView( child: Container( padding: const EdgeInsets.all(20), child: Column( children: [ FutureBuilder<Map<String, dynamic>>( future: fetchHomeScreenData(Localizations.localeOf(context).toString()), builder: (BuildContext context, AsyncSnapshot snapshot) { final result = AppHelper.apiResponseHandler(context, snapshot); if (result != null) return result; return Column(children: [ Consumer<FirebaseSubscriptionProvider>( builder: (context, firebaseSubscriptionProvider, _){ firebaseSubscriptionProvider.loadSubscriptionTopics(); final isSubscribed = firebaseSubscriptionProvider.isTopicSubscribed('new_post') ; final subscriptionColor = isSubscribed ? AppColors.orange : AppColors.green; return Container(); } ), ]); }, ), ], ), ), ), ); } }
同时简化助手函数:
static void toggleConnectivitySnackbar(BuildContext context, bool isConnected) { WidgetsBinding.instance.addPostFrameCallback((_) { final messenger = ScaffoldMessenger.of(context); if (!isConnected) { messenger.showSnackBar( SnackBar( content: Text(AppLocalizations.of(context)!.no_internet_connection), duration: const Duration(days: 1), backgroundColor: Theme.of(context).colorScheme.error, ), ); } else { messenger.clearSnackBars(); } }); }
方案2:在助手函数中判断当前页面是否为活跃首页
无需修改订阅逻辑,直接在助手函数中增加判断:仅当当前页面是首页且处于栈顶时,才显示SnackBar:
static void toggleConnectivitySnackbar(BuildContext context, ConnectivityProvider connectivityProvider) { WidgetsBinding.instance.addPostFrameCallback((_) { final currentRoute = ModalRoute.of(context); // 校验当前路由是首页且为栈顶页面 if (currentRoute?.settings.name == '/' && currentRoute?.isCurrent == true) { final messenger = ScaffoldMessenger.of(context); if (!connectivityProvider.isConnected) { messenger.showSnackBar( SnackBar( content: Text(AppLocalizations.of(context)!.no_internet_connection), duration: const Duration(days: 1), backgroundColor: Theme.of(context).colorScheme.error, ), ); } else { messenger.clearSnackBars(); } } }); }
方案3:全局路由监听控制
通过全局路由观察者记录当前页面,再结合网络状态判断是否显示SnackBar:
- 定义路由状态Provider:
class RouteProvider extends ChangeNotifier { String _currentRoute = '/'; String get currentRoute => _currentRoute; void updateRoute(String routeName) { _currentRoute = routeName; notifyListeners(); } }
- 实现路由观察者:
class AppRouteObserver extends NavigatorObserver { final RouteProvider routeProvider; AppRouteObserver(this.routeProvider); @override void didPush(Route route, Route? previousRoute) { super.didPush(route, previousRoute); if (route.settings.name != null) { routeProvider.updateRoute(route.settings.name!); } } @override void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); if (previousRoute?.settings.name != null) { routeProvider.updateRoute(previousRoute!.settings.name!); } } }
- 在MaterialApp中配置:
// 确保RouteProvider已在顶层注册 MaterialApp( navigatorObservers: [AppRouteObserver(Provider.of<RouteProvider>(context, listen: false))], // ...其他配置项 )
- 修改助手函数:
static void toggleConnectivitySnackbar(BuildContext context, ConnectivityProvider connectivityProvider) { WidgetsBinding.instance.addPostFrameCallback((_) { final routeProvider = Provider.of<RouteProvider>(context, listen: false); // 仅当前路由为首页时显示 if (routeProvider.currentRoute == '/') { final messenger = ScaffoldMessenger.of(context); if (!connectivityProvider.isConnected) { messenger.showSnackBar( SnackBar( content: Text(AppLocalizations.of(context)!.no_internet_connection), duration: const Duration(days: 1), backgroundColor: Theme.of(context).colorScheme.error, ), ); } else { messenger.clearSnackBars(); } } }); }
内容的提问来源于stack exchange,提问作者Usman M
相关产品推荐
相关产品推荐

