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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 12:36:18