Flutter - 每次关闭应用程序时存储对象列表的最佳方式?
Flutter - Best way to store a List of Objects every time I close the application?
情况:
我对 Flutter 和移动开发很陌生,因此我对 Dart 了解不多;而且我已经从有类似问题的人那里阅读了一些解决方案,但没有设法将这些解决方案用于我自己的事情。
问题:
我有一个有 2 个对象列表的待办事项应用程序,我想在用户重新打开应用程序时存储这些列表。
我知道它的简单内容,但由于缺乏经验,我觉得我正在努力war解决这个问题......所以我决定来寻求一些启发。
我试过的:
对于这个问题,我遇到了不同的解决方案,并且所有这些解决方案对于这种情况来说似乎都太复杂了(与我过去将列表保存到存档时所做的相比),包括:将列表编码为地图并转换为string 在使用 SharedPreferences 之前,使用 SQlite 数据库(我遇到的每个教程都让我觉得我会使用 war 坦克杀死一只蚂蚁,我对 firebase 也是如此)。
问题结构:
带有 ListView.builder 调用 2 个数组的 ToDo 屏幕:正在进行的任务和已完成的任务,我想在用户进行更改时分别写入 phone。 IDK 如果我应该只尝试通过调用一些包方法从它们所属的 class 中保存这些数组,或者我是否应该尝试存储 整个应用程序 如果这样的话事情是有可能的。
结论:
有没有办法以简单的方式解决这个问题,或者我应该为此使用像 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",
),
];
欢迎使用 Flutter 和 Dart!事情一开始可能看起来令人生畏,但后来会有所收获。从逻辑上考虑,数据如何存储在应用程序的状态之外?
它必须从一些数据存储中获取,这可以从设备存储中获取,也可以从远程数据库中获取。
对于设备存储,您有您提到的两个选项,但 SQlite 对这项任务来说太过分了,但是 sharedPreferences 并不像看起来那么令人生畏,而且一旦掌握它就非常容易使用.
它基本上是以字符串的形式将数据存储在设备上,以及用于稍后检索该数据的唯一键。这个键也是一个字符串。
对于您的应用,您只需要两个键:'tasks' & 'completedTasks'。
你哪里遇到麻烦了?将数据编码为字符串?还是先转成地图再编码?
你的另一个选择是远程数据库,你通过互联网发送数据,Firebase 只是可能的解决方案之一,除了构建你自己的服务器,API 和数据库,这绝对是对于这种情况也是矫枉过正。
但是,由于您仍不了解事情的窍门,这将是一个从共享首选项开始的好项目,然后在第二阶段研究 firebase,以便您的待办事项列表项可以跨多个设备同步。这还伴随着学习 firebase 的额外好处,我强烈建议您研究一下。
每一种新语言一开始都是令人生畏的,但是一开始就期望事情是 1+1=2 不会让你到达你想要的地方,而且我敢肯定你没有开始学习 Flutter 只是为了制作待办事项应用程序,但您从这个应用程序中学到的东西会让您为未来做好准备。
我的建议是,适应不舒服,记住你为什么开始这段旅程,无论你需要什么帮助,社区永远不会让你失望,只要遇见我们或半途而废。
所以一般来说,一旦您想存储原始类型以外的任何东西,即。 String
int
等等...事情变得有点复杂,因为它们必须转换为任何存储解决方案都可读的内容。
因此,尽管 Tasks
是一个带有几个字符串的基本对象,但 SharedPreferences 或其他任何东西都不知道 Task
是什么或如何使用它。
我建议一般阅读有关 json 序列化的内容,因为无论哪种方式,您都需要了解它。 This is a good place to start and 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});
}
在 运行 您的应用
之前将其放入您的主要方法中
await GetStorage.init();
您需要将 async
添加到您的 main 中,所以如果您不熟悉它的工作原理,它看起来像这样。
void main() async {
await GetStorage.init();
runApp(MyApp());
}
通常我永远不会在有状态的小部件中执行所有这些逻辑,而是实施状态管理解决方案并在 UI 之外的 class 中执行,但这是一个完全不同的讨论.我还建议您查看 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'),
),
],
),
);
}
}
情况:
我对 Flutter 和移动开发很陌生,因此我对 Dart 了解不多;而且我已经从有类似问题的人那里阅读了一些解决方案,但没有设法将这些解决方案用于我自己的事情。
问题:
我有一个有 2 个对象列表的待办事项应用程序,我想在用户重新打开应用程序时存储这些列表。
我知道它的简单内容,但由于缺乏经验,我觉得我正在努力war解决这个问题......所以我决定来寻求一些启发。
我试过的:
对于这个问题,我遇到了不同的解决方案,并且所有这些解决方案对于这种情况来说似乎都太复杂了(与我过去将列表保存到存档时所做的相比),包括:将列表编码为地图并转换为string 在使用 SharedPreferences 之前,使用 SQlite 数据库(我遇到的每个教程都让我觉得我会使用 war 坦克杀死一只蚂蚁,我对 firebase 也是如此)。
问题结构:
带有 ListView.builder 调用 2 个数组的 ToDo 屏幕:正在进行的任务和已完成的任务,我想在用户进行更改时分别写入 phone。 IDK 如果我应该只尝试通过调用一些包方法从它们所属的 class 中保存这些数组,或者我是否应该尝试存储 整个应用程序 如果这样的话事情是有可能的。
结论:
有没有办法以简单的方式解决这个问题,或者我应该为此使用像 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",
),
];
欢迎使用 Flutter 和 Dart!事情一开始可能看起来令人生畏,但后来会有所收获。从逻辑上考虑,数据如何存储在应用程序的状态之外? 它必须从一些数据存储中获取,这可以从设备存储中获取,也可以从远程数据库中获取。
对于设备存储,您有您提到的两个选项,但 SQlite 对这项任务来说太过分了,但是 sharedPreferences 并不像看起来那么令人生畏,而且一旦掌握它就非常容易使用.
它基本上是以字符串的形式将数据存储在设备上,以及用于稍后检索该数据的唯一键。这个键也是一个字符串。
对于您的应用,您只需要两个键:'tasks' & 'completedTasks'。
你哪里遇到麻烦了?将数据编码为字符串?还是先转成地图再编码?
你的另一个选择是远程数据库,你通过互联网发送数据,Firebase 只是可能的解决方案之一,除了构建你自己的服务器,API 和数据库,这绝对是对于这种情况也是矫枉过正。
但是,由于您仍不了解事情的窍门,这将是一个从共享首选项开始的好项目,然后在第二阶段研究 firebase,以便您的待办事项列表项可以跨多个设备同步。这还伴随着学习 firebase 的额外好处,我强烈建议您研究一下。
每一种新语言一开始都是令人生畏的,但是一开始就期望事情是 1+1=2 不会让你到达你想要的地方,而且我敢肯定你没有开始学习 Flutter 只是为了制作待办事项应用程序,但您从这个应用程序中学到的东西会让您为未来做好准备。
我的建议是,适应不舒服,记住你为什么开始这段旅程,无论你需要什么帮助,社区永远不会让你失望,只要遇见我们或半途而废。
所以一般来说,一旦您想存储原始类型以外的任何东西,即。 String
int
等等...事情变得有点复杂,因为它们必须转换为任何存储解决方案都可读的内容。
因此,尽管 Tasks
是一个带有几个字符串的基本对象,但 SharedPreferences 或其他任何东西都不知道 Task
是什么或如何使用它。
我建议一般阅读有关 json 序列化的内容,因为无论哪种方式,您都需要了解它。 This is a good place to start and 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});
}
在 运行 您的应用
之前将其放入您的主要方法中await GetStorage.init();
您需要将 async
添加到您的 main 中,所以如果您不熟悉它的工作原理,它看起来像这样。
void main() async {
await GetStorage.init();
runApp(MyApp());
}
通常我永远不会在有状态的小部件中执行所有这些逻辑,而是实施状态管理解决方案并在 UI 之外的 class 中执行,但这是一个完全不同的讨论.我还建议您查看 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'),
),
],
),
);
}
}