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 但更简单,因为您不需要针对不同数据类型的单独方法,只需 readwrite.

我不知道您是如何在应用程序中添加任务的,但这只是存储 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'),
          ),
        ],
      ),
    );
  }
}