You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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),
          ),
        );
      },
    );
  }
}

问题原因

  1. 首页的Consumer<ConnectivityProvider>在页面切换后未被销毁(首页仍保留在导航栈中),网络状态变化时仍会触发builder回调。
  2. 传递的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:

  1. 定义路由状态Provider:
class RouteProvider extends ChangeNotifier {
  String _currentRoute = '/';

  String get currentRoute => _currentRoute;

  void updateRoute(String routeName) {
    _currentRoute = routeName;
    notifyListeners();
  }
}
  1. 实现路由观察者:
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!);
    }
  }
}
  1. 在MaterialApp中配置:
// 确保RouteProvider已在顶层注册
MaterialApp(
  navigatorObservers: [AppRouteObserver(Provider.of<RouteProvider>(context, listen: false))],
  // ...其他配置项
)
  1. 修改助手函数:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 18:47:04