Flutter - 每次关闭应用程序时存储对象列表的最佳方式?

Lou*_*uis 6 storage list sharedpreferences dart flutter

情况:
我对 Flutter 和移动开发都很陌生,因此对 Dart 不太了解;我已经阅读了有类似问题的人的一些解决方案,但没有设法将这些解决方案应用于我自己的事情。

问题:
我有一个待办事项应用程序,它有 2 个对象列表,我想在用户重新打开应用程序时存储这些列表。

我知道这很简单,但我觉得由于缺乏经验,我正在解决这个问题……所以我决定来寻求一些帮助。

我尝试过的:
对于这个问题,我遇到了不同的解决方案,所有这些解决方案对于这种情况来说似乎都太复杂了(与我在将列表保存到存档时所做的相比),包括:将列表编码为地图并在使用 SharedPreferences 之前转换为字符串,使用 SQlite 数据库(我遇到的每个教程都让我觉得我会使用坦克来杀死蚂蚁,对于 firebase 我也会这么说)。

问题的结构:
带有 ListView.builder 的 ToDo 屏幕调用 2 个数组:正在进行的任务和已完成的任务,每当用户进行更改时,我都想将其中每个任务写入手机。我不知道我是否应该通过调用某些包方法来尝试从它们所属的类中保存这些数组,或者如果可能的话我是否应该尝试存储整个应用程序。

结论:
有没有办法以简单的方式解决这个问题,或者我应该使用像 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",
    ),
];
Run Code Online (Sandbox Code Playgroud)

Lor*_*n.A 6

2022 年更新,具有空安全性

我原来的代码示例过于冗长。使用 Dartsfactory构造函数可以用更少的代码来完成此操作。这也针对 null 安全性进行了更新,并使用Hive而不是 GetStorage。

首先,添加一个toMap将对象转换为 a 的方法Map,然后添加一个从保存在存储中的afromMap返回对象的构造函数。TaskMap

class Task {
  final String name;
  final String description;

  Task({required this.name, required this.description});

  Map<String, dynamic> toMap() {
    return {'name': this.name, 'description': this.description};
  }

  factory Task.fromMap(Map map) {
    return Task(
      name: map['name'],
      description: map['description'],
    );
  }

  String toString() {
    return 'name: $name description: $description';
  }
}
Run Code Online (Sandbox Code Playgroud)

更新的演示页面


class StorageDemo extends StatefulWidget {
  @override
  _StorageDemoState createState() => _StorageDemoState();
}

class _StorageDemoState extends State<StorageDemo> {
  List<Task> _tasks = [];

  final box = Hive.box('taskBox');

  // separate list for storing maps/ restoreTask function
  //populates _tasks from this list on initState

  List storageList = [];

  void addAndStoreTask(Task task) {
    _tasks.add(task);

    storageList.add(task.toMap()); // adding temp map to storageList
    box.put('tasks', storageList); // adding list of maps to storage
  }

  void restoreTasks() {
    storageList = box.get('tasks') ?? []; // initializing list from storage

// looping through the storage list to parse out Task objects from maps
    for (final map in storageList) {
      _tasks
          .add(Task.fromMap(map)); // adding Tasks back to your normal Task list
    }
  }

// looping through your list to see whats inside
  void printTasks() {
    for (final task in _tasks) {
      log(task.toString());
    }
  }

  void clearTasks() {
    _tasks.clear();
    storageList.clear();
    box.clear();
  }

  @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'),
          ),
        ],
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

更新存储初始化

void main() async {
  await Hive.initFlutter();
  await Hive.openBox('taskBox');
  runApp(MyApp());
}
Run Code Online (Sandbox Code Playgroud)

原答案

一般来说,一旦您想要存储原始类型以外的任何内容,即。String int等等...事情变得有点复杂,因为它们必须转换为任何存储解决方案都可以读取的内容。

因此,尽管TasksSharedPreferences 是一个带有几个字符串的基本对象,但它不知道 aTask是什么或如何处理它。

我建议一般性地阅读有关 json 序列化的内容,因为无论哪种方式您都需要了解它。这是一个很好的起点这里有另一篇关于它的好文章

话虽这么说,也可以在没有 json 的情况下通过将任务转换为 a Map(这就是 json 序列化所做的)并将其存储到映射列表来完成。我将向您展示一个不使用 json 手动执行此操作的示例。但再次强调,认真学习并花一些时间学习它对您最有利。

此示例将使用Get Storage,它类似于 SharedPreferences 但更简单,因为您不需要针对不同数据类型使用单独的方法,只需readwrite

我不知道您如何在应用程序中添加任务,但这只是存储Task对象列表的基本示例。任何不涉及在线存储的解决方案都需要在本地存储,并在应用程序启动时从存储中检索。

假设这是您的Task对象。

class Task {
  final String name;
  final String description;

  Task({this.name, this.description});
}
Run Code Online (Sandbox Code Playgroud)

在运行应用程序之前将其放入您的主要方法中

await GetStorage.init();
Run Code Online (Sandbox Code Playgroud)

您需要添加async到您的 main 中,所以如果您不熟悉它是如何工作的,它看起来像这样。

void main() async {
  await GetStorage.init();

  runApp(MyApp());
}
Run Code Online (Sandbox Code Playgroud)

通常,我永远不会在有状态小部件内执行所有这些逻辑,而是实现一个状态管理解决方案并在 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'),
          ),
        ],
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)