Flutter使用SharedPreferences保存字符串列表遇空指针错误及列表不显示问题
第一个问题:NoSuchMethodError: The method 'add' was called on null
这个错误的核心原因是categoryList被赋值成了null。看你的_update方法:
void _update() async { SharedPreferences prefs = await SharedPreferences.getInstance(); categoryList = prefs.getStringList('Categories'); }
当你第一次打开应用时,SharedPreferences里还没有保存任何分类数据,prefs.getStringList('Categories')会返回null,这就把原本初始化好的categoryList变成了null。之后调用categoryList.add(category)自然就会触发空指针错误。
第二个问题:ListView初始不显示
你的build方法里虽然写了默认列表的判断,但初始时categoryList是你声明的空列表List<String>(),并不是null,所以那个if (categoryList == null)的逻辑永远不会触发。再加上_update是异步方法,build执行时它还没完成,所以初始UI会显示空的ListView,直到弹窗操作触发setState才会刷新。
具体修复步骤
1. 把初始化逻辑移到initState
build方法会被Flutter频繁调用,不要把异步的_update放在里面,而是放在页面初始化的initState里,只执行一次:
@override void initState() { super.initState(); _update(); // 页面初始化时加载本地数据 }
2. 修复_update方法,避免categoryList为null
在获取SharedPreferences数据时,用空值合并运算符??给个默认列表,确保categoryList永远不会是null:
void _update() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { // 如果本地没有数据,就用默认的['Food', 'Clothes'] categoryList = prefs.getStringList('Categories') ?? ['Food', 'Clothes']; }); }
3. 修改_add和_delete方法,确保UI刷新
修改分类列表后,要调用setState通知Flutter更新UI,同时可以加个小优化:清空输入框、避免添加空字符串:
void _add(BuildContext context, String category) async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { categoryList.add(category); prefs.setStringList('Categories', categoryList); }); _textFieldController.clear(); // 添加后清空输入框 showSnackBar(context, 'Your category was added'); } void _delete(BuildContext context, String category) async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { categoryList.remove(category); prefs.setStringList('Categories', categoryList); }); showSnackBar(context, 'Your category was removed'); }
4. 清理build方法里的冗余代码
删掉build里的这部分代码,因为我们已经在initState和_update里处理了默认值:
if (categoryList == null) { setState(() { categoryList = ['Food', 'Clothes']; }); } _update();
5. 优化弹窗的添加逻辑
在弹窗的ADD按钮点击时,先判断输入内容不为空再添加,避免无效操作:
FlatButton( child: Text('ADD'), onPressed: () { String name = _textFieldController.text.trim(); if (name.isNotEmpty) { _add(context, name); } Navigator.of(context).pop(); }, ),
修复后的完整代码
import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; class Categories extends StatefulWidget { @override _CategoriesState createState() => _CategoriesState(); } class _CategoriesState extends State<Categories> { List<String> categoryList = []; TextEditingController _textFieldController = TextEditingController(); @override void initState() { super.initState(); _update(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Categories"), ), body: SafeArea( child: Container( color: Colors.white, child: getCategoriesListView(), ), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () { _displayDialog(context); }, ), ); } ListView getCategoriesListView() { return ListView.builder( itemCount: categoryList.length, itemBuilder: (context, int position) { return Card( color: Colors.white, elevation: 2.0, child: ListTile( title: Text(categoryList[position]), trailing: GestureDetector( child: Icon( Icons.delete, color: Colors.grey, ), onTap: () { _delete(context, categoryList[position]); }, ), ), ); }); } void _add(BuildContext context, String category) async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { categoryList.add(category); prefs.setStringList('Categories', categoryList); }); _textFieldController.clear(); showSnackBar(context, 'Your category was added'); } void _delete(BuildContext context, String category) async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { categoryList.remove(category); prefs.setStringList('Categories', categoryList); }); showSnackBar(context, 'Your category was removed'); } void _update() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { categoryList = prefs.getStringList('Categories') ?? ['Food', 'Clothes']; }); } void showSnackBar(BuildContext context, String message) async { final snackBar = SnackBar(content: Text(message)); Scaffold.of(context).showSnackBar(snackBar); } _displayDialog(BuildContext context) async { return showDialog( context: context, builder: (context) { return AlertDialog( title: Text('Add new category'), content: TextField( controller: _textFieldController, ), actions: <Widget>[ FlatButton( child: Text('ADD'), onPressed: () { String name = _textFieldController.text.trim(); if (name.isNotEmpty) { _add(context, name); } Navigator.of(context).pop(); }, ), FlatButton( child: Text('CANCEL'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }); } }
这样修改后,两个问题都能解决:categoryList永远不会是null,初始打开页面时会从SharedPreferences加载数据(没有的话用默认列表),ListView会正常显示,添加分类也不会报错了。
内容的提问来源于stack exchange,提问作者Frederik

