Flutter - 每次关闭应用程序时存储对象列表的最佳方式?
Posted
技术标签:
【中文标题】Flutter - 每次关闭应用程序时存储对象列表的最佳方式?【英文标题】:Flutter - Best way to store a List of Objects every time I close the application? 【发布时间】:2021-06-18 14:36:39 【问题描述】:情况: 我对 Flutter 和移动开发非常陌生,因此我对 Dart 不太了解;而且我从有类似问题的人那里阅读了一些解决方案,但没有设法将这些解决方案用于我自己的事情。
问题: 我有一个包含 2 个对象列表的待办事项应用程序,我想在用户重新打开应用程序时存储这些列表。
我知道它很简单,但由于缺乏经验,我觉得我正在努力解决这个问题......所以我决定来寻求一些启示。
我的尝试: 对于这个问题,我遇到了不同的解决方案,对于这种情况,所有这些解决方案似乎都太复杂了(与我在将列表保存到存档时所做的相比),包括:将列表编码为地图并转换为使用 SharedPreferences 之前的字符串,使用 SQlite 数据库(我遇到的每个教程都让我觉得我会使用战车杀死蚂蚁,我对 firebase 也是如此)。
问题的结构: 带有 ListView.builder 的 ToDo 屏幕调用 2 个数组:正在进行的任务和已完成的任务,每当用户进行更改时,我都想将它们写入手机。 IDK 如果我应该只尝试通过调用一些包方法从它们所属的类中保存这些数组,或者如果我应该尝试存储 整个应用程序(如果可能的话)。
结论: 有没有办法以简单的方式解决这个问题,或者我应该为此使用像firebase这样强大的东西?即使我不习惯使用 Firestore,所以我在黑暗中不知道如何应用这样的东西来保存数据。
我的列表的结构:
List<Task> _tasks = [
Task(
name: "do something",
description: "try to do it fast!!!",
),
];
List<Task> _doneTasks = [
Task(
name: "task marked as done",
description: "something",
),
];
【问题讨论】:
@Loren.A 感谢您的帮助!我设法与您提供的 get_storage 逻辑一起与提供者合作。我想知道如何从该存储列表中删除一个元素。我尝试做 storageList.remove('tasks') 但什么也没发生;当我尝试调用对象 box.remove('tasks') 时,应用程序崩溃了 - NoSuchMethodError: getter 'length' was called on null --- 这是在 storageList 元素之间的 restoreTasks 循环期间发生的。 【参考方案1】:一般来说,一旦你想存储除原始类型以外的任何东西,即。 String
int
等等...事情变得有点复杂,因为它们必须转换为任何存储解决方案都可以读取的东西。
因此,尽管 Tasks
是一个带有几个字符串的基本对象,但 SharedPreferences 或其他任何东西都不知道 Task
是什么或如何处理它。
我建议一般阅读有关 json 序列化的内容,因为无论哪种方式您都需要了解它。 This is a good place to start 和 here is another good article about it。
话虽如此,它也可以在没有 json 的情况下通过将您的任务转换为 Map
(这是 json 序列化无论如何)并将其存储到地图列表中来完成。我将向您展示一个不使用 json 手动执行此操作的示例。但是,再次强调,你最好还是好好学习一下。
本示例将使用Get Storage,它类似于 SharedPreferences,但更简单,因为您不需要针对不同数据类型的单独方法,只需 read
和 write
。
我不知道您是如何在应用程序中添加任务的,但这只是存储Task
对象列表的基本示例。任何不涉及在线存储的解决方案都需要在本地存储,并在应用启动时从存储中检索。
假设这里是您的 Task
对象。
class Task
final String name;
final String description;
Task(this.name, this.description);
在运行你的应用之前把它放在你的 main 方法中
await GetStorage.init();
您需要将async
添加到您的主目录中,所以如果您不熟悉它的工作原理,它看起来像这样。
void main() async
await GetStorage.init();
runApp(MyApp());
通常我永远不会在有状态小部件中执行所有这些逻辑,而是实现状态管理解决方案并在 UI 之外的类中执行,但这是一个完全不同的讨论。我还建议查看 GetX、Riverpod 或 Provider,了解它们,看看哪一个是最容易学习的。 GetX 在简单性和功能性方面得到了我的投票。
但由于您刚刚开始,我将省略这部分,暂时将所有这些功能放在 UI 页面中。
除了在应用关闭时才存储(也可以这样做),更容易在列表发生更改时随时存储。
这是一个页面,其中包含一些用于添加、清除和打印存储的按钮,因此您可以在应用重启后准确查看列表中的内容。
如果您了解这里发生了什么,您应该能够在您的应用程序中执行此操作,或者学习 json 并以这种方式执行此操作。无论哪种方式,您都需要了解Maps
以及本地存储如何与任何可用的解决方案一起使用。
class StorageDemo extends StatefulWidget
@override
_StorageDemoState createState() => _StorageDemoState();
class _StorageDemoState extends State<StorageDemo>
List<Task> _tasks = [];
final box = GetStorage(); // list of maps gets stored here
// separate list for storing maps/ restoreTask function
//populates _tasks from this list on initState
List storageList = [];
void addAndStoreTask(Task task)
_tasks.add(task);
final storageMap = ; // temporary map that gets added to storage
final index = _tasks.length; // for unique map keys
final nameKey = 'name$index';
final descriptionKey = 'description$index';
// adding task properties to temporary map
storageMap[nameKey] = task.name;
storageMap[descriptionKey] = task.description;
storageList.add(storageMap); // adding temp map to storageList
box.write('tasks', storageList); // adding list of maps to storage
void restoreTasks()
storageList = box.read('tasks'); // initializing list from storage
String nameKey, descriptionKey;
// looping through the storage list to parse out Task objects from maps
for (int i = 0; i < storageList.length; i++)
final map = storageList[i];
// index for retreival keys accounting for index starting at 0
final index = i + 1;
nameKey = 'name$index';
descriptionKey = 'description$index';
// recreating Task objects from storage
final task = Task(name: map[nameKey], description: map[descriptionKey]);
_tasks.add(task); // adding Tasks back to your normal Task list
// looping through you list to see whats inside
void printTasks()
for (int i = 0; i < _tasks.length; i++)
debugPrint(
'Task $i + 1 name $_tasks[i].name description: $_tasks[i].description');
void clearTasks()
_tasks.clear();
storageList.clear();
box.erase();
@override
void initState()
super.initState();
restoreTasks(); // restore list from storing in initState
@override
Widget build(BuildContext context)
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Container(),
),
TextButton(
onPressed: ()
final task =
Task(description: 'test description', name: 'test name');
addAndStoreTask(task);
,
child: Text('Add Task'),
),
TextButton(
onPressed: ()
printTasks();
,
child: Text('Print Storage'),
),
TextButton(
onPressed: ()
clearTasks();
,
child: Text('Clear Tasks'),
),
],
),
);
【讨论】:
【参考方案2】:欢迎使用 Flutter 和 Dart!事情在开始时可能看起来令人生畏,但后来会有所收获。从逻辑上考虑,数据如何存储在应用程序状态之外? 它必须从一些数据存储中获取,这可以从设备存储中获取,也可以从远程数据库中获取。
对于设备存储,你有你提到的两个选项,但 SQlite 对这个任务来说有点过头了,但是 sharedPreferences 并不像看起来那样令人生畏,而且一旦你掌握了它就很容易使用。
它基本上是以字符串的形式在设备上存储数据,以及稍后检索该数据的唯一键。这个键也是一个字符串。
对于您的应用,您只需要两个键:“tasks”和“completedTasks”。
你在哪里遇到麻烦?将数据编码为字符串?还是先转成地图再编码?
你的另一个选择是远程数据库,你通过互联网发送数据,Firebase 只是可能的解决方案之一,除了构建你自己的服务器、API 和数据库,这对于这种情况来说绝对是一种矫枉过正,因为好吧。
但是,由于您仍然掌握了窍门,这将是一个从共享偏好开始的好项目,然后在第二阶段,查看 firebase,以便您的 ToDo 列表项可以跨多个设备同步。这还带来了您从学习 firebase 中获得的额外好处,我强烈建议您研究一下。
每一种新语言一开始都是令人望而生畏的,但是从一开始就期望事情是 1+1=2 不会让你到达你想要的位置,而且我敢肯定你开始学习 Flutter 并不是为了只做 todo应用程序,但您从这个应用程序中学到的东西会让您为未来做好准备。
我的建议,适应不舒服,并记住你为什么开始这个旅程,无论你需要什么帮助,社区永远不会让你失望,只是认识我们或半途而废。
【讨论】:
以上是关于Flutter - 每次关闭应用程序时存储对象列表的最佳方式?的主要内容,如果未能解决你的问题,请参考以下文章
使用 Flutter 时我都有哪些存储数据的选项? (iOS和Android)[关闭]