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); } }
问题分析与解决方案
核心原因
- FutureBuilder重复执行异步任务:你直接在
FutureBuilder的future参数中调用fetchWallpapers(),每次SplashScreen因主题变化重建时,这个异步方法都会被重新执行,造成“应用重启”的错觉。 - 路由栈被意外重置:如果
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
相关产品推荐
相关产品推荐

