[Flutter学徒] 9 - 首选项(SharedPreferences)

2,270 阅读13分钟

本文由 简悦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图片,无需使用程序将其转换为矢量文件。

现在你已经看了一下这些库,在你开始为你的应用程序编码之前,花点时间考虑一下如何保存数据。

保存数据

有三种主要方式来保存数据到你的设备上。

  1. 写入格式化的数据,如JSON,到一个文件。
  2. 使用一个库或插件将简单的数据写到一个共享位置。
  3. 使用一个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关键字来表示这个方法将异步运行。它还

  1. 使用await关键字来等待SharedPreferences的实例。
  2. 使用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>[];
    }
  }
}


这个方法也是异步的。在这里,你

  1. 使用await关键字来等待一个SharedPreferences的实例。
  2. 检查你保存的列表的偏好是否已经存在。
  3. 获取以前的搜索列表。
  4. 如果列表为`空',则初始化一个空列表。

最后,在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();
    }
  });
}


在这个方法中,你。

  1. 通过调用setState()告诉系统重新绘制小部件。

  2. 清除当前的搜索列表,并重置计数、开始和结束位置。

  3. 检查以确保搜索文本没有被添加到以前的搜索列表中。

  4. 将搜索项添加到以前的搜索列表中。

  5. 保存以前的搜索。

添加一个搜索按钮

接下来,你将添加一个搜索按钮,在用户每次执行搜索时保存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代替了图标,用户可以点击它来进行搜索。

  1. 添加onPressed来处理点击事件。
  2. 使用当前的搜索文本来开始搜索。
  3. 通过使用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();
      },
    ),


在这段代码中,你。

  1. 添加一个TextField来输入你的搜索查询。

  2. 设置键盘动作为TextInputAction.done。当用户按下完成按钮时,这将关闭键盘。

  3. 当用户完成输入文本时,保存搜索。

  4. 创建一个PopupMenuButton来显示以前的搜索。

  5. 当用户从以前的搜索中选择一个项目时,开始一个新的搜索。

  6. 建立一个自定义下拉菜单列表(见widgets/custom_dropdown.dart)来显示以前的搜索。

  7. 如果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);
}



在这里:

  1. 使用await关键字来等待一个共享偏好插件的实例。
  2. 将选定的索引保存为整数。

现在,添加getCurrentIndex()

void getCurrentIndex() async {
  // 1
  final prefs = await SharedPreferences.getInstance();
  // 2
  if (prefs.containsKey(prefSelectedIndexKey)) {
    // 3
    setState(() {
      _selectedIndex = prefs.getInt(prefSelectedIndexKey);
    });
  }
}


通过这段代码,你。

  1. 使用await关键字来等待一个共享偏好插件的实例。
  2. 检查你当前索引的偏好是否已经存在。
  3. 获取当前索引并相应地更新状态。

现在,在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以准备从互联网上获取数据。到时见!


www.deepl.com 翻译