本文由 简悦SimpRead 转码,原文地址 www.raywenderlich.com
本章将教你如何将简单的数据保存到你的设备的本地存储中,在Android和......。
想象一下吧。你正在浏览菜谱,发现了一个你喜欢的菜。你很着急,想把它收藏起来以后再查看。你能建立一个能实现这个功能的Flutter应用吗?你当然可以! 继续阅读,了解如何做。
在本章中,你的目标是学习如何使用Shared_Preferences插件,将重要的信息保存到你的设备上。
你将从一个新的项目开始,在屏幕底部显示三个标签,用于三种不同的视图:Recipes, Bookmarks and Groceries.
第一个屏幕是你搜索你想准备的食谱的地方。一旦你找到了你喜欢的食谱,只需将其加入书签,该应用程序将把食谱添加到你的Bookmarks页面,同时将你需要的所有原料添加到你的购物清单中。你将使用网络API来搜索菜谱,并将你收藏的菜谱存储在本地数据库中。
完成后的应用程序将看起来像。
这显示了你搜索pasta时得到的Recipes标签结果。这就像在搜索文本字段中输入并按下搜索图标一样简单。该应用程序在文本字段右侧的组合框中存储了你的搜索词历史。
当你点击一个卡片时,你会看到类似的东西。
要保存一个食谱,只需点击Bookmark按钮。当你浏览到Bookmark标签时,你会看到该食谱已被保存。
如果你不想要这个菜谱了,向左或向右滑动,你会看到一个删除按钮,允许你从书签菜谱列表中删除它。
Groceries标签显示了你制作书签上的食谱所需的原料。
你将在接下来的几章中建立这个应用程序。在这一章中,你将使用shared preferences来保存简单的数据,如所选的标签,同时也将缓存食谱标签中搜索到的项目。
在本章结束时,你将知道。
- 什么是shared preferences。
- 如何使用shared_preferences插件来保存和检索对象。
现在你知道了你的目标是什么,是时候开始行动了!
开始使用
在Android Studio中打开本章的启动项目,如果有必要,运行flutter pub get
,然后运行该应用程序。
注意底部的三个标签--当你点击它时,每个标签都会显示不同的屏幕。目前只有菜谱界面UI显示。它看起来像这样。
App libraries
启动项目在pubspec.yaml中包括以下库。
dependencies:
...
cached_network_image: ^2.5.0
flutter_slidable: ^0.5.7
flutter_svg: ^0.19.3
以下是它们帮助你做的事情。
cached_network_image:下载并缓存你将在应用程序中使用的图像。 flutter_slidable: 建立一个小部件,让用户左右滑动卡片来执行不同的操作,比如删除保存的食谱。 flutter_svg: 加载SVG图片,无需使用程序将其转换为矢量文件。
现在你已经看了一下这些库,在你开始为你的应用程序编码之前,花点时间考虑一下如何保存数据。
保存数据
有三种主要方式来保存数据到你的设备上。
- 写入格式化的数据,如JSON,到一个文件。
- 使用一个库或插件将简单的数据写到一个共享位置。
- 使用一个SQLite数据库。
将数据写入文件很简单,但它需要你以正确的格式和顺序处理数据的读写。
你也可以使用一个库或插件,把简单的数据写到平台管理的共享位置,比如iOS和Android。这就是你在本章要做的事情。
对于更复杂的数据,你可以把信息保存到本地数据库。你会在以后的章节中了解更多这方面的知识。
为什么要保存小段的数据?
有许多理由要保存小部分数据。例如,你可以在用户登录时保存用户ID--或者用户是否登录过。你也可以保存用户的入职状态,或者保存用户在书签上的数据,以便以后查阅。
注意,当用户卸载应用程序时,这些保存在shared prefesrence上的简单数据会丢失。
shared_preferences插件
shared_preferences是一个Flutter插件,它允许你以键值格式保存数据,这样你以后就可以轻松地检索它。在幕后,它在安卓系统中使用恰如其分的SharedPreferences,在iOS中使用类似的UserDefaults。
对于这个应用程序,你将学会使用这个插件,保存用户输入的搜索词以及当前选择的标签。
这个插件的好处之一是它不需要任何设置或配置。只要创建一个插件的实例,你就可以获取和保存数据了。
注意。shared_preferences插件为你提供了一个快速保存和检索数据的方法,但它只支持保存简单的属性,如字符串、数字和布尔值。
在后面的章节中,你会了解到当你想保存复杂数据时可以使用的替代方法。
请注意,shared_preferences并不适合存储敏感数据。要存储密码或访问令牌,请查看Android的Keystore和iOS的Keychain服务,或考虑使用flutter_secure_storage插件。
要使用shared_preferences,你需要首先将其作为一个依赖项添加。打开pubspec.yaml,在flutter_svg
库下面,添加。
shared_preferences: ^2.0.5
确保你的缩进方式与其他库相同。
现在,点击Pub Get按钮,获得shared_preferences库。
你也可以在命令行中运行pub get。
flutter pub get
你现在准备好存储数据了。你将从保存用户的搜索开始,这样他们就可以在未来轻松地再次选择它们。
保存UI状态
你将在这一部分使用shared_preferences来保存已保存的搜索列表。稍后,你还会保存用户选择的标签,这样应用程序就会一直打开到那个标签。
你将从准备你的搜索来存储这些信息开始。
在搜索列表中添加一个条目
首先,你要改变用户界面,以便当用户按下搜索图标时,应用程序会将搜索条目添加到搜索列表中。
打开ui/recipes/recipe_list.dart,之后。
import 'package:flutter/material.dart';
添加:
import 'package:shared_preferences/shared_preferences.dart';
import '.../widgets/custom_dropdown.dart';
import '.../colors.dart';
这就导入了shared_preferences插件,一个显示下拉菜单的自定义部件和一个设置颜色的辅助类。
接下来,你将给每个搜索词一个唯一的键。之后。
class _RecipeListState extends State<RecipeList> {
添加。
static const String prefSearchKey = 'previousSearches';
所有的偏好都需要使用一个唯一的键,否则它们会被覆盖掉。在这里,你只是为偏好键定义了一个常量。
接下来,在这个变量之后。
bool inErrorState = false;
添加。
List<String> previousSearches = <String>[];
这为你保存用户以前的搜索和跟踪当前的搜索扫清了道路。
在后台运行代码
为了理解你接下来要添加的代码,你需要了解一下在后台运行代码的情况。
大多数现代UI都有一个主线程来运行UI代码。任何需要很长时间的代码都需要在不同的线程或进程中运行,这样它就不会阻塞UI。Dart使用一种类似于JavaScript的技术来实现这一点。该语言包括这两个关键字。
async
await
async
标记一个方法或代码部分为异步。然后在该方法中使用await
关键字来等待,直到一个异步进程在后台完成。
Dart也有一个名为Future
的类,它表示该方法承诺了一个未来的结果。SharedPreferences.getInstance()
返回Future<SharedPreferences>
,你用它来检索SharedPreferences
类的实例。接下来你会看到它的作用。
保存以前的搜索
现在你已经奠定了一些基础,你已经准备好实现保存搜索。
还是在recipe_list.dart中,在dispose()
之后,添加以下方法。
void savePreviousSearches() async {
// 1
final prefs = await SharedPreferences.getInstance();
// 2
prefs.setStringList(prefSearchKey, previousSearches);
}
在这里,你使用async
关键字来表示这个方法将异步运行。它还
- 使用
await
关键字来等待SharedPreferences
的实例。 - 使用
prefSearchKey
键保存以前的搜索列表。
接下来,添加以下方法。
void getPreviousSearches() async {
// 1
final prefs = await SharedPreferences.getInstance();
// 2
if (prefs.containsKey(prefSearchKey)) {
// 3
previousSearches = prefs.getStringList(prefSearchKey);
// 4
if (previousSearches == null) {
previousSearches = <String>[];
}
}
}
这个方法也是异步的。在这里,你
- 使用
await
关键字来等待一个SharedPreferences
的实例。 - 检查你保存的列表的偏好是否已经存在。
- 获取以前的搜索列表。
- 如果列表为`空',则初始化一个空列表。
最后,在initState()
中的super.initState()
后面添加。
getPreviousSearches();
当用户重新启动应用程序时,这将加载所有以前的搜索。
添加搜索功能
为了执行搜索,你需要清除任何变量并保存新的搜索值。这个方法还不会进行实际的搜索。通过在_buildSearchCard()
方法之后添加startSearch()
方法来实现。
void startSearch(String value) {
// 1
setState(() {
// 2
currentSearchList.clear();
currentCount = 0;
currentEndPosition = pageCount;
currentStartPosition = 0;
hasMore = true;
// 3
if (!previousSearches.contains(value)) {
// 4
previousSearches.add(value);
// 5
savePreviousSearches();
}
});
}
在这个方法中,你。
-
通过调用
setState()
告诉系统重新绘制小部件。 -
清除当前的搜索列表,并重置计数、开始和结束位置。
-
检查以确保搜索文本没有被添加到以前的搜索列表中。
-
将搜索项添加到以前的搜索列表中。
-
保存以前的搜索。
添加一个搜索按钮
接下来,你将添加一个搜索按钮,在用户每次执行搜索时保存keyword。
在_buildSearchCard
()中,把const Icon(Icons.search)
替换为以下内容。
IconButton(
icon: const Icon(Icons.search),
// 1
onPressed: () {
// 2
startSearch(searchTextController.text);
// 3
final currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
),
这样就用一个IconButton
代替了图标,用户可以点击它来进行搜索。
- 添加
onPressed
来处理点击事件。 - 使用当前的搜索文本来开始搜索。
- 通过使用
FocusScope
类来隐藏键盘。
接下来,将// *** Start Replace
和 // *** End Replace
之间的所有内容替换为。
Expanded(
// 3
child: TextField(
decoration: const InputDecoration(
border: InputBorder.none, hintText: 'Search'),
autofocus: false,
// 4
textInputAction: TextInputAction.done,
// 5
onSubmitted: (value) {
if (!previousSearches.contains(value)) {
previousSearches.add(value);
savePreviousSearches();
}
},
controller: searchTextController,
)),
// 6
PopupMenuButton<String>(
icon: const Icon(
Icons.arrow_drop_down,
color: lightGrey,
),
// 7
onSelected: (String value) {
searchTextController.text = value;
startSearch(searchTextController.text);
},
itemBuilder: (BuildContext context) {
// 8
return previousSearches
.map<CustomDropdownMenuItem<String>>((String value) {
return CustomDropdownMenuItem<String>(
text: value,
value: value,
callback: () {
setState(() {
// 9
previousSearches.remove(value);
Navigator.pop(context);
});
},
);
}).toList();
},
),
在这段代码中,你。
-
添加一个
TextField
来输入你的搜索查询。 -
设置键盘动作为
TextInputAction.done
。当用户按下完成按钮时,这将关闭键盘。 -
当用户完成输入文本时,保存搜索。
-
创建一个
PopupMenuButton
来显示以前的搜索。 -
当用户从以前的搜索中选择一个项目时,开始一个新的搜索。
-
建立一个自定义下拉菜单列表(见widgets/custom_dropdown.dart)来显示以前的搜索。
-
如果X图标被按下,从以前的搜索中删除搜索,并关闭弹出菜单。
为了显示以前的文本搜索列表,你使用了一个带有下拉菜单的文本字段。这就是一个带有文本字段'和
自定义下拉菜单项'的行。该菜单项显示搜索词和右边的图标。它看起来会像这样。
点击X将从列表中删除相应的条目。
测试应用程序
现在是测试应用程序的时候了。因为你添加了一个新的依赖,所以退出正在运行的实例并再次运行它(注意,在添加依赖时你并不总是需要重新启动)。你会看到类似这样的东西。
PopupMenuButton
在点击时显示一个菜单,当用户选择一个菜单项时调用onSelected()
方法。
输入一个食品项目,如pasta,并确保当你点击搜索按钮时,应用程序将你的搜索项添加到下拉列表中。
不要担心进度圈的运行--那是在没有数据的时候发生的。当你点击下拉箭头时,你的应用程序应该看起来像这样。
现在,通过点击红色的停止按钮来停止该应用程序。
再次运行该应用程序,并点击下拉按钮。意大利面的条目就在那里。是时候庆祝一下了 :]
下一步是用同样的方法来保存所选标签。
保存所选标签
在这一节中,你将使用shared_preferences来保存用户已经导航到的当前UI标签。
打开main_screen.dart,添加以下导入。
import 'package:shared_preferences/shared_preferences.dart';
接下来。
List<Widget> pageList = <Widget>[];
之后添加:
static const String prefSelectedIndexKey = 'selectedIndex';
这是你用于选定索引偏好键的常量。
接下来,在initState()
后面添加这个新方法。
void saveCurrentIndex() async {
// 1
final prefs = await SharedPreferences.getInstance();
// 2
prefs.setInt(prefSelectedIndexKey, _selectedIndex);
}
在这里:
- 使用
await
关键字来等待一个共享偏好插件的实例。 - 将选定的索引保存为整数。
现在,添加getCurrentIndex()
。
void getCurrentIndex() async {
// 1
final prefs = await SharedPreferences.getInstance();
// 2
if (prefs.containsKey(prefSelectedIndexKey)) {
// 3
setState(() {
_selectedIndex = prefs.getInt(prefSelectedIndexKey);
});
}
}
通过这段代码,你。
- 使用
await
关键字来等待一个共享偏好插件的实例。 - 检查你当前索引的偏好是否已经存在。
- 获取当前索引并相应地更新状态。
现在,在initState()
中添加以下内容作为最后一行。
getCurrentIndex();
这将在页面加载时检索出当前选择的索引。
最后,当用户点击一个标签时,你需要调用saveCurrentIndex()
。
要做到这一点,在_onItemTapped()
的末尾添加以下内容。
saveCurrentIndex();
这将在用户每次选择不同的标签时保存当前索引。
现在,建立并运行该应用程序,选择第二个或第三个标签。
退出应用程序并再次运行,以确保应用程序启动时使用保存的索引。
在这一点上,你的应用程序应该显示一个先前搜索的项目列表,并且当你再次启动应用程序时,也会带你到最后选择的标签。下面是一个例子。
恭喜你! 你已经为当前的标签和用户以前的搜索都保存了状态。
关键点
- 在一个应用程序中,有多种方法来保存数据:保存在文件中、共享偏好中和SQLite数据库中。
- 共享偏好最好用于存储简单的、原始类型的键值对,如字符串、数字和布尔。
- 使用共享偏好的一个例子是保存用户正在查看的标签,这样用户下次启动应用程序时就会被带到同一个标签。
async
/await
关键字对让你在主UI线程之外运行异步代码,然后等待响应。一个例子是获得一个SharedPreferences
的实例。- shared_preferences插件不应该被用来保存敏感数据。相反,考虑使用flutter_secure_storage插件。
从这里开始去哪里?
在本章中,你学习了如何使用shared_preferences插件在你的应用程序中持久化简单的数据类型。
如果你想了解更多关于Android的SharedPreferences,请到developer.android.com/reference/k…。
对于iOS,请查看UserDefaults developer.apple.com/documentati…。
在下一章中,你将继续构建同一个应用程序,并学习如何序列化JSON以准备从互联网上获取数据。到时见!