Flutter:如何让TextFormField初始值同步SharedPreferences存储的计数器?
问题分析与解决方案
没关系,新手遇到这种异步加载的问题太正常了,咱们来一步步拆解问题~
你的核心需求是让TextFormField的初始值和存在SharedPreferences里的counter保持一致,但现在重启后文本框显示0,问题出在这两点:
_loadCounter是异步方法(用到了await),App启动时第一次执行build方法的时候,_counter还是初始的0;TextFormField的initialValue只会在Widget第一次初始化时生效,哪怕之后_counter通过setState更新了,输入框也不会自动同步这个值。
下面给你两种实用的解决方案,你可以根据需求选:
方案一:使用TextEditingController(推荐,更灵活)
TextEditingController是Flutter专门用来控制输入框内容的工具,能随时动态更新输入框的文本,非常适合你的场景。
修改代码的步骤:
- 定义一个
TextEditingController变量; - 在
initState里初始化它,等SharedPreferences加载完counter后,更新controller的文本; - 点击递增按钮时,同时更新
_counter和controller的文本; - 记得在页面销毁时释放controller,避免内存泄漏。
修改后的完整代码:
import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Shared preferences demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Shared preferences demo'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; // 1. 定义TextEditingController TextEditingController _textController; @override void initState() { super.initState(); // 2. 初始化controller _textController = TextEditingController(); _loadCounter(); } _loadCounter() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { _counter = (prefs.getInt('counter') ?? 0); // 加载完成后更新输入框内容 _textController.text = '$_counter'; }); } _incrementCounter() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { _counter = (prefs.getInt('counter') ?? 0) + 1; prefs.setInt('counter', _counter); // 点击递增时同步更新输入框 _textController.text = '$_counter'; }); } @override void dispose() { // 3. 页面销毁时释放controller _textController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), // 4. 把initialValue换成controller TextFormField( controller: _textController, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } }
方案二:用FutureBuilder等待加载完成
如果你不想用Controller,可以用FutureBuilder包裹TextFormField,等_loadCounter执行完成后再渲染输入框,这样初始值就会直接是保存的counter值。
修改build方法里的TextFormField部分即可:
// 替换原来的TextFormField FutureBuilder( future: _loadCounter(), // 等待加载完成 builder: (context, snapshot) { // 加载完成后再显示输入框 if (snapshot.connectionState == ConnectionState.done) { return TextFormField( initialValue: '$_counter', ); } // 加载中可以显示一个加载提示 return CircularProgressIndicator(); }, )
注意:这种方式下,点击递增按钮后,_counter更新会触发setState,FutureBuilder会重新执行_loadCounter,不过因为SharedPreferences是本地存储,性能影响很小,也能正常工作。
新手小提示
- 异步操作(比如
await SharedPreferences.getInstance())不会阻塞UI,但会延后执行,所以要注意变量更新的时机; - 用
TextEditingController不仅能控制初始值,还能随时读取或修改输入框的内容,后续如果需要让输入框和其他组件联动,会非常方便; - 所有的
Controller在页面销毁时都要记得调用dispose(),避免内存泄漏哦~
内容的提问来源于stack exchange,提问作者BrennoL
相关产品推荐
相关产品推荐

