Flutter Web点击首页按钮报'Scheduling update'焦点错误求助
Web端Flutter按钮点击抛出焦点错误排查
问题现象
Web端运行Flutter应用时,点击首页任意按钮都会抛出错误:Scheduling update, current focus is $_primaryFocus, next focus will be $_markedForFocus,但移动端和桌面端运行完全正常,且未手动使用任何focus()实例或相关焦点操作。
相关代码
HomeScreen 组件
class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { return ScaffoldMessenger( child: Scaffold( drawer: const MyDrawer(), appBar: AppBar( title: const Text("Tela inicial"), actions: const [ IconSwitchTheme(), ], titleTextStyle: TextStyle( fontSize: MediaQuery.of(context).size.width > 400 ? MediaQuery.of(context).size.width > 700 ? 40.0 : 24.0 : 19.0), ), body: Center( child: SizedBox( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width * 0.9, child: Column( children: [ SizedBox( height: MediaQuery.of(context).size.height < 700 ? MediaQuery.of(context).size.height * 0.15 : MediaQuery.of(context).size.height * 0.2, width: double.infinity, child: Image.asset("assets/images/labsec-logo.png"), ), Center( child: Text( "Desafio App", style: MediaQuery.of(context).size.height < 700 ? Theme.of(context).textTheme.bodyLarge : Theme.of(context) .textTheme .bodyLarge! .copyWith(fontSize: 50.0), ), ), const SizedBox(height: 30), Expanded( child: SizedBox( width: MediaQuery.of(context).size.width > 700 ? MediaQuery.of(context).size.width * 0.45 : MediaQuery.of(context).size.width * 0.85, child: GridView.count( padding: const EdgeInsets.all(8.0), shrinkWrap: true, // physics: const NeverScrollableScrollPhysics(), crossAxisCount: 2, mainAxisSpacing: 15.0, crossAxisSpacing: 15.0, children: [ CustomButton( onPressed: () => Navigator.of(context) .pushNamed("/bluetooth-devices"), child: const HomeScreenButtonChild( icon: Icons.bluetooth, text: "Dispositivos LBE", ), ), CustomButton( onPressed: () => Navigator.of(context).pushNamed('/rsa-keys'), child: const HomeScreenButtonChild( icon: Icons.key, text: "Gerar chaves RSA", ), ), CustomButton( onPressed: () => Navigator.of(context).pushNamed('/sign-list'), child: const HomeScreenButtonChild( icon: Icons.edit_note, text: "Assinar lista", ), ), CustomButton( onPressed: () => Navigator.of(context) .pushNamed('/verify-signature'), child: const HomeScreenButtonChild( icon: Icons.search, text: "Verificar assinatura", ), ), ], ), ), ), const SizedBox(height: 20), ], ), ), ), ), ); } }
CustomButton 组件
class CustomButton extends StatelessWidget { const CustomButton({ super.key, required this.onPressed, required this.child, this.style, this.spinner, }); final VoidCallback? onPressed; final ButtonStyle? style; final bool? spinner; final Widget child; @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onPressed, style: style, child: spinner != null ? const CircularProgressIndicator() : child, ); } }
HomeScreenButtonChild 组件
class HomeScreenButtonChild extends StatelessWidget { const HomeScreenButtonChild({ super.key, required this.icon, required this.text, }); final IconData icon; final String text; @override Widget build(BuildContext context) { return Column( children: [ const SizedBox(height: 10.0), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Icon(icon, size: MediaQuery.of(context).size.width > 700 ? MediaQuery.of(context).size.width > 1000 ? 120.0 : 80.0 : 40.0), ], ), ), const SizedBox(height: 10.0), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Flexible( child: Text( text, textAlign: TextAlign.center, style: TextStyle( fontSize: MediaQuery.of(context).size.width > 700 ? MediaQuery.of(context).size.width > 1000 ? 40.0 : 20.0 : 16.0), ), ), ], ), ) ], ); } }
MyDrawer 组件
class MyDrawer extends StatelessWidget { const MyDrawer({super.key}); @override Widget build(BuildContext context) { final ThemeMode themeMode = context.watch<ThemeProvider>().themeMode; return Drawer( backgroundColor: themeMode == ThemeMode.light ? Colors.white : Theme.of(context).colorScheme.primary, child: ListView( children: [ DrawerHeader( child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text( "CONFIGURAÇÕES", style: Theme.of(context).textTheme.titleLarge!.copyWith( color: themeMode == ThemeMode.light ? Theme.of(context).colorScheme.primary : Colors.white, ), ), IconButton( icon: const Icon(Icons.close), iconSize: 30, color: themeMode == ThemeMode.light ? Theme.of(context).colorScheme.primary : Colors.white, onPressed: () => Navigator.of(context).pop(), ) ], ), ), ), // const Divider(), ListTile( leading: const Icon( Icons.delete, color: Colors.red, ), title: Text( "Deletar todos os dados", style: Theme.of(context).textTheme.headlineMedium, ), onTap: () { context.read<BluetoothDevicesProvider>().clearProvider(); context.read<RSAKeysProvider>().clearProvider(); context.read<SignatureProvider>().clearProvider(); }, ) ], ), ); } }
环境配置
[✓] Flutter (Channel stable, 3.7.3, on macOS 13.2.1 22D68 darwin-arm64, locale en-US) • Flutter version 3.7.3 on channel stable at /opt/homebrew/Caskroom/flutter/3.7.3/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 9944297138 (2 weeks ago), 2023-02-08 15:46:04 -0800 • Engine revision 248290d6d5 • Dart version 2.19.2 • DevTools version 2.20.1 [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2) • Android SDK at /Users/enrico/Library/Android/sdk • Platform android-33, build-tools 33.0.2 • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14C18 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2022.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) [✓] VS Code (version 1.75.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.58.0 [✓] Connected device (2 available) • macOS (desktop) • macos • darwin-arm64 • macOS 13.2.1 22D68 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 110.0.5481.100 [✓] HTTP Host Availability • All required HTTP hosts are available • No issues found!
原因分析
这个错误是Flutter Web端焦点管理的内部冲突问题,和手动调用焦点API无关,主要诱因包括:
- Flutter 3.7.3版本的Web端焦点处理存在已知bug,页面导航时焦点状态更新逻辑冲突
ElevatedButton在Web端默认会自动获取焦点,跳转页面时原按钮的焦点未被正确释放,导致焦点树更新异常ScaffoldMessenger嵌套在HomeScreen内部,可能干扰了全局焦点树的正常层级结构
解决方案
1. 升级Flutter版本
直接升级到3.7.5及以上的稳定版,官方已经修复了该版本批次的Web焦点相关bug,这是最彻底的解决方式。
2. 手动清除焦点再导航
在按钮的onPressed回调中,先主动清除当前焦点,再执行页面跳转:
CustomButton( onPressed: () { // 清除当前焦点 FocusManager.instance.primaryFocus?.unfocus(); Navigator.of(context).pushNamed("/bluetooth-devices"); }, // ...其他参数 )
3. 调整ScaffoldMessenger结构
将ScaffoldMessenger移到MaterialApp的子组件层级,而非嵌套在HomeScreen中,确保全局消息处理和焦点树的正确关联:
// 示例结构 MaterialApp( home: ScaffoldMessenger( child: HomeScreen(), ), );
4. 禁用按钮自动焦点
给ElevatedButton添加autofocus: false属性,避免按钮自动获取焦点引发冲突:
// 在CustomButton的build方法中 return ElevatedButton( autofocus: false, // 添加这一行 onPressed: onPressed, style: style, child: spinner != null ? const CircularProgressIndicator() : child, );
内容的提问来源于stack exchange,提问作者bringand1
相关产品推荐
相关产品推荐

