Flutter切换ThemeMode时HomePage多次触发build的解决咨询
问题
在Flutter应用中使用BLoC(GetX也存在相同问题)进行状态管理时,点击切换ThemeMode的按钮,发现HomePage的build方法被多次调用,控制台重复打印flutter: >>>>> call build <<<<<<。排查发现移除按钮点击事件中的Theme.of(context)代码后问题解决,现需在保留相关逻辑的前提下避免多次build。
代码示例
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ThemeCubit extends Cubit<ThemeMode> { ThemeCubit() : super(ThemeMode.light); void toggleTheme() { emit(state == ThemeMode.light ? ThemeMode.dark : ThemeMode.light); } } void main() async { runApp(BlocProvider(create: (_) => ThemeCubit(), child: const MyApp())); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return BlocBuilder<ThemeCubit, ThemeMode>( builder: (context, themeMode) { return MaterialApp( home: const MyHomePage(), themeMode: themeMode, darkTheme: ThemeData( brightness: Brightness.dark, visualDensity: VisualDensity.standard, ), theme: ThemeData( visualDensity: VisualDensity.standard, ), ); }, ); } } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { debugPrint(">>>>> call build <<<<<<"); return Scaffold( body: Column( children: [ ElevatedButton( onPressed: () { // 仅调用此方法就会在切换ThemeMode时触发多次build Theme.of(context); context.read<ThemeCubit>().toggleTheme(); }, child: const Text("Change ThemeMode"), ), ], ), ); } }
运行结果
flutter: >>>>> call build <<<<<< flutter: >>>>> call build <<<<<< flutter: >>>>> call build <<<<<< flutter: >>>>> call build <<<<<< flutter: >>>>> call build <<<<<< flutter: >>>>> call build <<<<<< flutter: >>>>> call build <<<<<<
原因分析
在onPressed回调中调用Theme.of(context)时,会让MyHomePage订阅Theme的变化。切换ThemeMode时,MaterialApp会更新Theme触发MyHomePage重建;同时BLoC状态更新又会触发MaterialApp重建,进而再次触发MyHomePage重建,形成循环导致多次build。
解决方法
方法1:提前缓存Theme,避免回调中订阅
在build方法内提前获取并缓存Theme,不在onPressed中调用Theme.of(context):
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { // 提前在build阶段获取Theme并缓存 final theme = Theme.of(context); debugPrint(">>>>> call build <<<<<<"); return Scaffold( body: Column( children: [ ElevatedButton( onPressed: () { // 使用缓存的theme执行逻辑 print(theme.brightness); context.read<ThemeCubit>().toggleTheme(); }, child: const Text("Change ThemeMode"), ), ], ), ); } }
方法2:转为StatefulWidget,在生命周期中获取Theme
如果需要处理Theme变化的生命周期逻辑,可转为StatefulWidget,在didChangeDependencies中获取Theme:
class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { late ThemeData _theme; @override void didChangeDependencies() { super.didChangeDependencies(); // 仅在依赖变化时更新Theme,避免回调中触发订阅 _theme = Theme.of(context); } @override Widget build(BuildContext context) { debugPrint(">>>>> call build <<<<<<"); return Scaffold( body: Column( children: [ ElevatedButton( onPressed: () { print(_theme.brightness); context.read<ThemeCubit>().toggleTheme(); }, child: const Text("Change ThemeMode"), ), ], ), ); } }
方法3:用Builder隔离上下文
若必须在回调中访问Theme,用Builder包裹按钮,让Builder的上下文单独订阅Theme变化,不影响MyHomePage:
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { debugPrint(">>>>> call build <<<<<<"); return Scaffold( body: Column( children: [ Builder( builder: (btnContext) { return ElevatedButton( onPressed: () { // 使用Builder的上下文获取Theme,仅触发Builder重建 final theme = Theme.of(btnContext); print(theme.brightness); context.read<ThemeCubit>().toggleTheme(); }, child: const Text("Change ThemeMode"), ); }, ), ], ), ); } }
内容的提问来源于stack exchange,提问作者maaoi
相关产品推荐
相关产品推荐

