Flutter切换两个MaterialApp时出现瞬时黑屏问题求助
Flutter 切换MaterialApp出现瞬时黑屏问题解决
问题说明
我的Flutter应用会在加载所有页面前执行服务检查,根据检查结果展示不同页面。但两个页面都包含独立的MaterialApp,当服务检查通过后从一个MaterialApp切换到另一个时,会出现瞬时黑屏。
注:应用顶层有包裹MaterialApp的Provider结构,未获取服务检查数据就调用该结构会报错,因此必须根据检查结果使用两个不同的MaterialApp。
原代码
class MyApp extends Stateful Widget { @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { bool isServiceAddressChecked = false; bool isThereInternetConnectionError = false; @override void initState() { super.initState(); checkServiceAddress(); } @override Widget build(BuildContext context) { return isServiceAddressChecked ? OverlaySupport.global( child: ChangeNotifierProvider( create: (context) => UserViewModel(), child: MaterialApp( navigatorKey: MaterialKeyService.materialKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, theme: ThemeData(fontFamily: 'Geogrotesque'), debugShowCheckedModeBanner: false, home: LoaderOverlay(child: LandingPage()), ), ), ) : MaterialApp( navigatorKey: MaterialKeyService.materialKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, theme: ThemeData(fontFamily: 'Geogrotesque'), debugShowCheckedModeBanner: false, home: Scaffold( body: Container( color: const Color(0xff007ac1), child: isThereInternetConnectionError ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text( "internetError".tr(), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Container( height: 45, width: 200, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/registerbuton.png"), fit: BoxFit.fill, )), child: TextButton( onPressed: () async { setState(() { isServiceAddressChecked = false; isThereInternetConnectionError = false; }); await checkServiceAddress(); }, child: Text( "tryAgain2".tr().toString(), style: const TextStyle(color: Color(0xff007ac1), fontWeight: FontWeight.normal), ), ), ), ), ], ), ) : const Center( child: SizedBox( height: 0, width: 0, ), ), ), ), ); } Future<void> getServiceAddress() async { var apiData = await getServiceAddressApi(); apiAddress = apiData['apiAddress']; socketAddress = apiData['socketAddress']; } Future<dynamic> getServiceAddressApi() async { try { var response = await http.get(Uri.parse("https://something")); if (response.statusCode >= 200 && response.statusCode <= 299) { var apiData = utf8.decode(response.bodyBytes); var apiDataMap = jsonDecode(apiData); return apiDataMap; } else { var responseMap = jsonDecode(utf8.decode(response.bodyBytes)); if (responseMap['message'] != null) throw Exception(responseMap['message']); throw Exception("httpError"); } } on SocketException { throw Exception('internetError'); } on HttpException { throw Exception('httpError'); } on FormatException { throw Exception('httpError'); } } Future<void> checkServiceAddress() async { try { await getServiceAddress(); setState(() { isServiceAddressChecked = true; }); } catch (e) { if (e.toString().substring(11) == "internetError") { setState(() { isThereInternetConnectionError = true; }); } } } }
解决方案
黑屏的核心原因是切换两个独立的MaterialApp会触发Flutter重新构建整个应用顶层组件树,导致短暂的视觉断层。以下是两种优化方案:
方案1:共享顶层MaterialApp,仅切换Home内容
将MaterialApp提升到外层作为固定顶层组件,内部根据服务检查状态切换home属性的内容,同时延迟初始化Provider直到服务检查完成。这种方式避免了整个应用顶层的重建:
class MyApp extends Stateful Widget { @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { bool isServiceAddressChecked = false; bool isThereInternetConnectionError = false; @override void initState() { super.initState(); checkServiceAddress(); } @override Widget build(BuildContext context) { return MaterialApp( navigatorKey: MaterialKeyService.materialKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, theme: ThemeData(fontFamily: 'Geogrotesque'), debugShowCheckedModeBanner: false, home: isServiceAddressChecked ? OverlaySupport.global( child: ChangeNotifierProvider( create: (context) => UserViewModel(), child: LoaderOverlay(child: LandingPage()), ), ) : Scaffold( body: Container( color: const Color(0xff007ac1), child: isThereInternetConnectionError ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text( "internetError".tr(), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Container( height: 45, width: 200, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/registerbuton.png"), fit: BoxFit.fill, )), child: TextButton( onPressed: () async { setState(() { isServiceAddressChecked = false; isThereInternetConnectionError = false; }); await checkServiceAddress(); }, child: Text( "tryAgain2".tr().toString(), style: const TextStyle(color: Color(0xff007ac1), fontWeight: FontWeight.normal), ), ), ), ), ], ), ) : const Center( child: CircularProgressIndicator(color: Colors.white), ), ), ), ); } // 以下方法保持原逻辑不变 Future<void> getServiceAddress() async { var apiData = await getServiceAddressApi(); apiAddress = apiData['apiAddress']; socketAddress = apiData['socketAddress']; } Future<dynamic> getServiceAddressApi() async { try { var response = await http.get(Uri.parse("https://something")); if (response.statusCode >= 200 && response.statusCode <= 299) { var apiData = utf8.decode(response.bodyBytes); var apiDataMap = jsonDecode(apiData); return apiDataMap; } else { var responseMap = jsonDecode(utf8.decode(response.bodyBytes)); if (responseMap['message'] != null) throw Exception(responseMap['message']); throw Exception("httpError"); } } on SocketException { throw Exception('internetError'); } on HttpException { throw Exception('httpError'); } on FormatException { throw Exception('httpError'); } } Future<void> checkServiceAddress() async { try { await getServiceAddress(); setState(() { isServiceAddressChecked = true; }); } catch (e) { if (e.toString().contains("internetError")) { setState(() { isThereInternetConnectionError = true; }); } } } }
方案2:使用FutureBuilder简化状态管理
如果不想用StatefulWidget的setState,可以用FutureBuilder直接处理服务检查的异步状态,代码更简洁且避免手动状态管理:
class MyApp extends StatelessWidget { Future<bool> _performServiceCheck() async { try { var apiData = await getServiceAddressApi(); apiAddress = apiData['apiAddress']; socketAddress = apiData['socketAddress']; return true; } on SocketException { return false; } catch (_) { return false; } } @override Widget build(BuildContext context) { return MaterialApp( navigatorKey: MaterialKeyService.materialKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, theme: ThemeData(fontFamily: 'Geogrotesque'), debugShowCheckedModeBanner: false, home: FutureBuilder<bool>( future: _performServiceCheck(), builder: (context, snapshot) { // 加载中状态 if (snapshot.connectionState == ConnectionState.waiting) { return Scaffold( backgroundColor: const Color(0xff007ac1), body: const Center(child: CircularProgressIndicator(color: Colors.white)), ); } // 检查失败或出错状态 if (!snapshot.hasData || !snapshot.data!) { return Scaffold( backgroundColor: const Color(0xff007ac1), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "internetError".tr(), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700), ), const SizedBox(height: 16), Container( height: 45, width: 200, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/registerbuton.png"), fit: BoxFit.fill, ), ), child: TextButton( onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => MyApp())), child: Text( "tryAgain2".tr().toString(), style: const TextStyle(color: Color(0xff007ac1), fontWeight: FontWeight.normal), ), ), ), ], ), ), ); } // 检查成功状态 return OverlaySupport.global( child: ChangeNotifierProvider( create: (context) => UserViewModel(), child: LoaderOverlay(child: LandingPage()), ), ); }, ), ); } // getServiceAddressApi方法保持原逻辑不变 Future<dynamic> getServiceAddressApi() async { try { var response = await http.get(Uri.parse("https://something")); if (response.statusCode >= 200 && response.statusCode <= 299) { var apiData = utf8.decode(response.bodyBytes); var apiDataMap = jsonDecode(apiData); return apiDataMap; } else { var responseMap = jsonDecode(utf8.decode(response.bodyBytes)); if (responseMap['message'] != null) throw Exception(responseMap['message']); throw Exception("httpError"); } } on SocketException { throw Exception('internetError'); } on HttpException { throw Exception('httpError'); } on FormatException { throw Exception('httpError'); } } }
额外优化建议
- 将空容器替换为
CircularProgressIndicator,给用户明确的加载反馈,提升体验 - 异常处理中使用
contains替代字符串截取,避免因异常格式变化导致的错误 - 可以考虑定义自定义异常类,让错误匹配更可靠
内容的提问来源于stack exchange,提问作者Melik BOZKURT
相关产品推荐
相关产品推荐

