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

Flutter切换主题模式时应用自动重启问题求助

Flutter切换主题模式时应用自动重启问题求助

问题描述

我现在遇到一个Flutter应用的问题:当我尝试切换应用的主题模式时,整个应用会自动重启,闪屏页会重新加载Supabase上的数据,就像应用刚启动一样。我的应用结构是:

  • 闪屏页(SplashScreen)使用FutureBuilder加载远程壁纸数据
  • 通过ThemeProvider(搭配shared_preferences)管理主题状态
  • MaterialApp关联主题,启动后先进入闪屏页,数据加载完成后跳转主页

相关代码

闪屏页代码

import 'package:dot/main.dart';
import 'package:dot/services/theme_data.dart';
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class SplashScreen extends StatefulWidget {
  const SplashScreen({super.key});

  @override
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  Future<List<Map<String, dynamic>>> fetchWallpapers() async {
    final response = await Supabase.instance.client
        .from('png table')
        .select('*')
        .order('id', ascending: true);
    return List<Map<String, dynamic>>.from(response);
  }

  @override
  Widget build(BuildContext context) {
    final clr = Theme.of(context).brightness == Brightness.light
        ? primaryClr
        : fourth;
    return Scaffold(
      body: FutureBuilder<List<Map<String, dynamic>>>(
        future: fetchWallpapers(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Text(
                    "•Png",
                    style: TextStyle(
                      fontFamily: "SouthWind",
                      fontSize: 90,
                      color: clr,
                    ),
                  ),
                  SizedBox(
                    width: MediaQuery.of(context).size.width * 0.4,
                    child: const Column(
                      children: [
                        LinearProgressIndicator(
                          backgroundColor: Colors.transparent,
                        ),
                        Padding(
                          padding: EdgeInsets.only(top: 20.0),
                          child: Text("Loading."),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            );
          }
          if (snapshot.hasError) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    'Failed to load PNGs',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: clr,
                      fontSize: 15,
                    ),
                  ),
                  SizedBox(height: MediaQuery.of(context).size.height * 0.15),
                  const Text(
                    'We are facing problem loading the PNGs\nPlease check your network and try again.',
                    textAlign: TextAlign.center,
                  ),
                ],
              ),
            );
          }
          if (snapshot.hasData) {
            debugPrint("Fetched ${snapshot.data!.length} PNGs");
            return NavPage(snapshot: snapshot);
          }
          return const SizedBox();
        },
      ),
    );
  }
}

ThemeProvider代码

import 'package:dot/services/theme_data.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class ThemeProvider extends ChangeNotifier {
  ThemeData _themeData = lightMode;
  ThemeData get themeData => _themeData;
  bool get isDarkMode => _themeData == darkMode;

  ThemeProvider() {
    _loadTheme();
  }

  set themeData(ThemeData themeData) {
    _themeData = themeData;
    notifyListeners();
    _saveTheme();
  }

  void toggleTheme() {
    if (_themeData == lightMode) {
      themeData = darkMode;
    } else {
      themeData = lightMode;
    }
  }

  Future<void> _loadTheme() async {
    final prefs = await SharedPreferences.getInstance();
    final themeMode = prefs.getString('theme') ?? 'light';
    _themeData = (themeMode == 'dark') ? darkMode : lightMode;
    notifyListeners();
  }

  Future<void> _saveTheme() async {
    final prefs = await SharedPreferences.getInstance();
    final themeMode = (_themeData == darkMode) ? 'dark' : 'light';
    await prefs.setString('theme', themeMode);
  }
}

问题分析与解决方案

核心原因

  1. FutureBuilder重复执行异步任务:你直接在FutureBuilder的future参数中调用fetchWallpapers(),每次SplashScreen因主题变化重建时,这个异步方法都会被重新执行,造成“应用重启”的错觉。
  2. 路由栈被意外重置:如果MaterialApp因主题变化重建,而SplashScreen是初始路由,会导致路由栈回到初始状态,重新进入闪屏页。

解决步骤

1. 避免FutureBuilder重复执行异步任务

在initState中初始化Future,确保异步任务只执行一次:

class _SplashScreenState extends State<SplashScreen> {
  late final Future<List<Map<String, dynamic>>> _wallpapersFuture;

  @override
  void initState() {
    super.initState();
    // 仅在Widget初始化时执行一次异步请求
    _wallpapersFuture = fetchWallpapers();
  }

  Future<List<Map<String, dynamic>>> fetchWallpapers() async {
    final response = await Supabase.instance.client
        .from('png table')
        .select('*')
        .order('id', ascending: true);
    return List<Map<String, dynamic>>.from(response);
  }

  @override
  Widget build(BuildContext context) {
    final clr = Theme.of(context).brightness == Brightness.light
        ? primaryClr
        : fourth;
    return Scaffold(
      body: FutureBuilder<List<Map<String, dynamic>>>(
        future: _wallpapersFuture, // 使用初始化好的Future
        builder: (context, snapshot) {
          // 原有builder逻辑保持不变
          ...
        },
      ),
    );
  }
}
2. 替换路由而非保留闪屏页在栈中

数据加载完成后,用pushReplacement替换路由,将闪屏页从路由栈中移除,避免主题变化时回到闪屏页:

// 在SplashScreen的FutureBuilder中,当snapshot.hasData时:
if (snapshot.hasData) {
  debugPrint("Fetched ${snapshot.data!.length} PNGs");
  // 使用postFrameCallback确保Widget树构建完成后再跳转
  WidgetsBinding.instance.addPostFrameCallback((_) {
    Navigator.pushReplacement(
      context,
      MaterialPageRoute(
        builder: (context) => HomePage(wallpapers: snapshot.data!),
      ),
    );
  });
  // 返回临时空Widget,因为路由即将替换
  return const Scaffold(body: SizedBox());
}
3. 优化MaterialApp的主题配置

确保主题切换时仅更新必要部分,避免MaterialApp不必要的重建:

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);
    return MaterialApp(
      title: 'My App',
      theme: lightMode,
      darkTheme: darkMode,
      // 用themeMode控制当前主题,而非直接替换ThemeData
      themeMode: themeProvider.isDarkMode ? ThemeMode.dark : ThemeMode.light,
      initialRoute: '/',
      routes: {
        '/': (context) => const SplashScreen(),
        '/home': (context) => const HomePage(),
      },
    );
  }
}

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 11:23:02