Flutter中如何确保StatelessWidget里GlobalKey的currentState已初始化
问题:如何在仅渲染一次的StatelessWidget中确保GlobalKey的currentState已初始化?
我编写了如下StatelessWidget代码:
class StatelessStreamPage extends StatelessWidget { final _formKey = GlobalKey<FormBuilderState>(); // (a) StatelessStreamPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Stateless Stream Page'), ), body: FormBuilder( key: _formKey, child: Column( children: [ FormBuilderTextField(name: 'textField'), StreamBuilder<FormBuilderFields>( stream: _formKey.currentState?.onChanged, // (b) builder: (context, snapshot) { return Text(snapshot.data.toString()); }, ), ], ), ), ); } }
在(a)行,我初始化了FormBuilderState类型的GlobalKey,同时需要使用StreamBuilder。在(b)行调用_formKey.currentState时,首次渲染其必然为null,且StatelessWidget仅渲染一次。请问如何确保该GlobalKey的currentState已完成初始化(不为null)?
环境说明
- 使用
flutter_form_builder库,正在实现该库的一个PR - Flutter Doctor详情:
[✓] Flutter (Channel stable, 3.3.9, on Fedora Linux 36 (KDE Plasma) 6.0.17-200.fc36.x86_64, locale en_US.UTF-8) • Flutter version 3.3.9 on channel stable at /home/erayerdin/.local/lib/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision b8f7f1f986 (7 weeks ago), 2022-11-23 06:43:51 +0900 • Engine revision 8f2221fbef • Dart version 2.18.5 • DevTools version 2.15.0 [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) • Android SDK at /home/erayerdin/.sdks/android/ • Platform android-33, build-tools 33.0.0 • ANDROID_SDK_ROOT = /home/erayerdin/.sdks/android • Java binary at: /var/lib/snapd/snap/android-studio/125/android-studio/jre/bin/java • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866) • All Android licenses accepted. [✓] Chrome - develop for the web • Chrome at google-chrome [✓] Linux toolchain - develop for Linux desktop • clang version 14.0.5 (Fedora 14.0.5-2.fc36) • cmake version 3.25.1 • ninja version 1.10.2 • pkg-config version 1.8.0 [✓] Android Studio (version 2021.3) • Android Studio at /var/lib/snapd/snap/android-studio/125/android-studio • 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.13+0-b1751.21-8125866) [✓] VS Code • VS Code at /snap/code/current • Flutter extension version 3.56.0 [✓] Connected device (2 available) • Linux (desktop) • linux • linux-x64 • Fedora Linux 36 (KDE Plasma) 6.0.17-200.fc36.x86_64 • Chrome (web) • chrome • web-javascript • Google Chrome 108.0.5359.124 [✓] HTTP Host Availability • All required HTTP hosts are available • No issues found!
解决方案
由于StatelessWidget的build方法执行时,关联的FormBuilder还未完成挂载,因此_formKey.currentState会是null。以下是几种适配StatelessWidget的解决方式:
1. 用StreamController中转流,挂载后绑定
通过StreamController作为中间层,在Widget完成首次渲染后获取FormBuilder的流并转发:
class StatelessStreamPage extends StatelessWidget { final _formKey = GlobalKey<FormBuilderState>(); final _streamController = StreamController<FormBuilderFields>.broadcast(); StatelessStreamPage({super.key}) { WidgetsBinding.instance.addPostFrameCallback((_) { final formStream = _formKey.currentState?.onChanged; if (formStream != null) { formStream.listen(_streamController.add); } }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Stateless Stream Page'), ), body: FormBuilder( key: _formKey, child: Column( children: [ FormBuilderTextField(name: 'textField'), StreamBuilder<FormBuilderFields>( stream: _streamController.stream, builder: (context, snapshot) { return Text(snapshot.data?.toString() ?? '无数据'); }, ), ], ), ), ); } }
注意:StatelessWidget无生命周期dispose方法,若需避免内存泄漏,可考虑结合其他方式管理StreamController的生命周期,或直接转为StatefulWidget。
2. 用Builder和PostFrameCallback触发重绘
在StreamBuilder外层包裹Builder,在首次渲染完成后获取currentState并标记重绘:
class StatelessStreamPage extends StatelessWidget { final _formKey = GlobalKey<FormBuilderState>(); StatelessStreamPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Stateless Stream Page'), ), body: FormBuilder( key: _formKey, child: Column( children: [ FormBuilderTextField(name: 'textField'), Builder(builder: (context) { Stream<FormBuilderFields>? stream; WidgetsBinding.instance.addPostFrameCallback((_) { stream = _formKey.currentState?.onChanged; // 标记当前Element需要重绘 (context as Element).markNeedsBuild(); }); return StreamBuilder<FormBuilderFields>( stream: stream, builder: (context, snapshot) { return Text(snapshot.data?.toString() ?? '无数据'); }, ); }), ], ), ), ); } }
这种方式通过重绘StreamBuilder,确保后续能获取到已初始化的currentState。
3. 推荐:转为StatefulWidget(更稳妥)
虽然要求使用Stateless,但StatefulWidget能更自然地处理生命周期,避免内存泄漏等问题:
class StatefulStreamPage extends StatefulWidget { const StatefulStreamPage({super.key}); @override State<StatefulStreamPage> createState() => _StatefulStreamPageState(); } class _StatefulStreamPageState extends State<StatefulStreamPage> { final _formKey = GlobalKey<FormBuilderState>(); Stream<FormBuilderFields>? _formStream; @override void didChangeDependencies() { super.didChangeDependencies(); // 依赖变更后,FormBuilder已完成挂载,可获取currentState _formStream = _formKey.currentState?.onChanged; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Stateful Stream Page'), ), body: FormBuilder( key: _formKey, child: Column( children: [ FormBuilderTextField(name: 'textField'), StreamBuilder<FormBuilderFields>( stream: _formStream, builder: (context, snapshot) { return Text(snapshot.data?.toString() ?? '无数据'); }, ), ], ), ), ); } }
内容的提问来源于stack exchange,提问作者Eray Erdin
相关产品推荐
相关产品推荐

