Flutter数据存储管理插件entity_store的使用

Flutter数据存储管理插件EntityStore的使用

简介

EntityStore通过围绕实体进行状态管理来增强Flutter应用开发。该库将应用的业务逻辑封装在不可变实体中,并通过集中式状态管理保持UI的一致性。

以下TodoTile组件示例演示了EntityStore如何连接UI组件与它们的状态。

class TodoTile extends StatelessWidget {
  // ...

  @override
  Widget build(BuildContext context) {
    // 监控特定的Todo
    final todo = context.watchOne<int, Todo>(todoId)!;

    // 切换Todo的完成状态
    return CheckboxListTile(
      title: Text(todo.name),
      value: todo.isDone,
      onChanged: (bool? value) {
        if (value != null) {
          todoRepository.save(todo.toggle());
        }
      },
    );
  }
}

特点

  • 反应式UI同步:EntityStore会根据状态变化在UI上做出反应。例如,在上述代码示例中,watchOne方法用于监控特定的Todo实体,并在状态变化时更新复选框显示。
  • 简洁的状态更新:实体的状态仅在必要时通过新实例进行更新。通过调用todo.toggle()来切换Todo的完成状态,并通过仓库持久化结果。
  • 中心处理实体:EntityStore专注于构成应用核心部分的实体,使开发者能够专注于业务逻辑。
  • 灵活的数据库集成:EntityStore有助于与外部数据源集成。通过替换仓库实现,可以连接到Firebase Firestore、本地数据库或其他存储。
  • 减少样板代码:使用预先准备好的仓库实现消除了开发者编写重复数据库操作代码的需求。这加速了开发过程并简化了应用的维护。

安装

要在您的Flutter项目中引入EntityStore包,请在pubspec.yaml文件中添加以下内容:

dependencies:
  entity_store: latest_version

使用

以下示例代码演示了如何使用EntityStore实现一个Todo应用。

Todo实体的定义

实体封装了应用的核心业务逻辑,并具有唯一的标识符(ID)。这使得在整个应用中可以识别实体,保持数据完整性。在EntityStore中,实体被设计为不可变的。这对于检测实体状态的变化并确保其在UI中正确反映非常重要。

Todo类是一个实现了这一概念的例子。每个Todo项都有一个唯一的ID、名称和完成状态属性。create工厂方法允许您创建一个新的Todo实例,并随机生成名称。toggle方法用于切换Todo的完成状态,返回一个带有更新状态的新Todo实例。

class Todo implements Entity<int> {
  // 实体属性必须是不可变的。
  @override
  final int id;
  final String name;
  final bool isDone;

  Todo(this.id, this.name, this.isDone);

  // 创建新的Todo实体
  factory Todo.create(int id) {
    return Todo(
      id,
      getRandomTodoName(),
      false,
    );
  }

  // 切换Todo的完成状态
  Todo toggle() {
    return Todo(
      id,
      name,
      !isDone,
    );
  }
}

这种不可变的设计方法允许每个Todo实例可重用,并且只能通过创建新实例来更改状态,防止意外副作用,使状态管理更加可预测。它还减少了检测实体变化和更新UI时的复杂性,提高了应用的可维护性。

Todo仓库的实现

EntityStore的一个强大功能是实现了仓库模式。遵循此模式,TodoRepository继承自LocalStorageRepository,并通过覆盖继承的方法定义自己的保存、读取和删除实体的方式。

TodoRepositoryLocalStorageRepository的一个特定实现,用于Todo实体。它覆盖了fromJsontoJson方法,以便于从JSON数据读取和保存实体,从而便于在实体和数据存储之间转换。

class TodoRepository extends LocalStorageRepository<int, Todo> {
  TodoRepository(super.controller, super.localStorageHandler);

  @override
  Todo fromJson(Map<String, dynamic> json) {
    // 从JSON创建一个Todo实体
  }

  @override
  Map<String, dynamic> toJson(Todo entity) {
    // 将一个Todo实体转换为JSON
  }
}

关于LocalStorageHandler

EntityStore包默认提供了InMemoryStorageHandler,这是一个简单的存储处理器,仅在内存中存储数据。这使得开发和测试期间的状态管理变得容易。但在实际应用中,您可以实现自定义的LocalStorageHandler以将数据存储在设备的本地存储中。

class InMemoryStorageHandler extends LocalStorageHandler {
  // 在内存中进行数据操作
}

EntityStore的设置

要设置EntityStore,初始化EntityStoreNotifierEntityStoreController,并与仓库关联如下:

final entityStoreNotifier = EntityStoreNotifier();
final entityStoreController = EntityStoreController(entityStoreNotifier);
final storageHandler = InMemoryStorageHandler();
final todoRepository = TodoRepository(entityStoreController, storageHandler);

虽然上述代码片段为了简单起见使用了全局变量,但建议使用依赖注入(DI),例如使用Riverpod的Provider或GetIt。这样做可以有效地解析应用不同部分所需的依赖关系,提高测试性和代码的可重用性。

示例:

final entityStoreProvider = Provider((ref) => EntityStoreNotifier());
final entityStoreControllerProvider = Provider((ref) => EntityStoreController(ref.watch(entityStoreProvider)));
// ...

// 在应用的其他部分
final entityStoreNotifier = ref.watch(entityStoreProvider);
final entityStoreController = ref.watch(entityStoreControllerProvider);

采用这种方法,可以使应用各个部分所需的各种依赖关系更加明确,并实现一种能应对变化和扩展的设计。

在UI中的使用

设置EntityStoreProviderScope

在使用EntityStore包的第一步是在应用的顶层设置一个EntityStoreProviderScope。这为整个应用范围内的状态监控和共享奠定了基础。

class MyApp extends StatelessWidget {
  // ...

  @override
  Widget build(BuildContext context) {
    // 在应用根部放置EntityStoreProviderScope
    return EntityStoreProviderScope(
      entityStoreNotifier: entityStoreNotifier,
      child: MaterialApp(
        // ...
      ),
    );
  }
}

监控状态并更新UI

您可以使用selectAll来获取所有Todo实体的ID,并在UI中作为列表显示。这样,您可以监控整个Todo列表,并在有新的Todo添加时更新列表。

context.watchOne监控特定的实体。通过利用这些方法,状态的变化会自动反映在UI中。

class MyHomePage extends StatefulWidget {
  // ...

  @override
  Widget build(BuildContext context) {
    // 获取所有Todos的ID
    final todoIds = context.selectAll<int, Todo, List<int>>(
      (value) => value.ids.toList(),
    );

    // ...
  }
}

执行实体操作并保持UI一致性

当您通过仓库操作实体时,EntityStore中的状态会被更新,相关的UI组件也会自动更新。

class TodoTile extends StatelessWidget {
  // ...

  @override
  Widget build(BuildContext context) {
    // 监控特定的Todo
    final todo = context.watchOne<int, Todo>(todoId);

    // 切换Todo的完成状态
    return CheckboxListTile(
      title: Text(todo.name),
      value: todo.isDone,
      onChanged: (bool? value) {
        if (value != null) {
          // 通过仓库更新实体
          todoRepository.save(todo.toggle());
        }
      },
    );
  }
}

状态管理方法的详细说明和使用示例

EntityStore提供的watchselectread方法是监视应用状态并相应地更新UI的重要工具。这些方法帮助保持EntityStore管理和UI组件之间的同步。

watch方法的使用

您可以使用watchAll方法实时监控未完成的Todo,并在发生变化时更新列表。

final activeTodos = context.watchAll<int, Todo>(
  (todo) => !todo.isDone,
);

ListView.builder(
  itemCount: activeTodos.length,
  itemBuilder: (context, index) {
    final todo = activeTodos.values.elementAt(index);
    return ListTile(
      title: Text(todo.name),
      leading: Checkbox(
        value: todo.isDone,
        onChanged: (bool? checked) {
          // 逻辑以更新Todo的完成状态
        },
      ),
    );
  },
);

您可以使用watchOne方法来监控特定Todo实体的状态变化,并仅更新那个Todo。

final todo = context.watchOne<int, Todo>(todoId);

if (todo != null) {
  return CheckboxListTile(
    title: Text(todo.name),
    value: todo.isDone,
    onChanged: (bool? newValue) {
      // 逻辑以更新Todo的状态
    },
  );
}

select方法的使用

您可以使用selectAll方法从整个Todo列表中检索特定数据(例如名称)并仅显示那些数据。

final todoNames = context.selectAll<int, Todo, List<String>>(
  (todos) => todos.values.map((todo) => todo.name).toList(),
);

ListView(
  children: todoNames.map((name) => Text(name)).toList(),
);

您可以使用selectOne方法从单个Todo中检索特定数据,并在Todo的名称发生变化时更新显示该数据的小部件。

final todoName = context.selectOne<int, Todo, String>(
  todoId,
  (todo) => todo.name,
);

Text(todoName ?? 'No name');

read方法的使用

您可以使用readAll方法在屏幕初始加载时一次性检索所有Todos,并在不触发重建的情况下使用数据。

final allTodos = context.readAll<int, Todo>();

// 在初始加载时显示所有Todos
ListView(
  children: allTodos.values.map((todo) => Text(todo.name)).toList(),
);

您可以使用readOne方法基于用户操作执行一次性数据读取,例如在对话框中显示Todo详情。

final todo = context.readOne<int, Todo>(todoId);

// 显示由用户操作触发的对话框
showDialog(
  context: context,
  builder: (context) {
    return AlertDialog(
      title: Text(todo?.name ?? 'No name'),
      content: Text('已完成: ${todo?.isDone}'),
    );
  },
);

通过使用这些方法,您可以高效地读取和更新EntityStore中的状态,同时确保应用状态的变化能够在UI中按需反映。

仓库的使用

仓库旨在抽象数据源操作并将业务逻辑与数据访问代码分离。在EntityStore中,通过仓库执行的实体操作会自动反映在UI中。这意味着当您通过仓库保存、更新或删除实体时,这些更改会自动传播到UI,允许用户立即看到最新的状态。

这种行为是通过使用反应式方法(如watchselect等)结合EntityStore实现的,确保EntityStore管理和UI组件之间的同步。以下是基本的仓库操作及其使用示例。

findById

获取指定ID的实体。

// 示例:通过ID查找Todo
var result = await todoRepository.findById(todoId);
if (result.isOk) {
  var todo = result.ok;
  // 执行Todo相关操作
}

findAll

检索所有实体。

// 示例:获取满足特定条件的所有Todos
var result = await todoRepository.query()
  .where('isComplete', isEqualTo: false)
  .findAll();
if (result.isOk) {
  var todos = result.ok;
  // 执行Todos列表上的操作
}

findOne

检索符合特定条件的第一个实体。

// 示例:查找符合特定条件的第一个Todo
var result = await todoRepository.query()
  .where('isComplete', isEqualTo: false)
  .findOne();
if (result.isOk) {
  var todo = result.ok;
  // 执行Todo相关操作
}

count

计算符合特定条件的实体数量。

// 示例:计算未完成的Todos数量
var result = await todoRepository.query()
  .where('isComplete', isEqualTo: false)
  .count();
if (result.isOk) {
  var activeCount = result.ok;
  // 使用activeCount执行操作
}

save

保存或更新一个实体。

// 示例:保存一个新Todo
var newTodo = Todo.create(name: '新任务');
var result = await todoRepository.save(newTodo);
if (result.isOk) {
  // 处理保存操作的成功
}

delete

删除一个实体。

// 示例:删除一个Todo
var result = await todoRepository.delete(todoId);
if (result.isOk) {
  // 处理删除操作的成功
}

upsert

如果实体不存在则创建,如果存在则更新。

// 示例:Upsert一个Todo
var result = await todoRepository.upsert(
  todoId,
  creater: () => Todo.create(name: '新任务'),
  updater: (existingTodo) => existingTodo.copyWith(isComplete: true),
);
if (result.isOk) {
  // 处理Upsert操作的成功
}

更多关于Flutter数据存储管理插件entity_store的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数据存储管理插件entity_store的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


entity_store 是一个用于 Flutter 应用的数据存储管理插件,它提供了一种简单且高效的方式来管理应用中的数据实体。通过 entity_store,你可以轻松地进行数据的增删改查操作,并且它还支持数据的变化监听和状态管理。

安装

首先,你需要在 pubspec.yaml 文件中添加 entity_store 依赖:

dependencies:
  flutter:
    sdk: flutter
  entity_store: ^0.1.0  # 请根据最新版本进行更新

然后运行 flutter pub get 来安装依赖。

基本用法

1. 创建实体

首先,你需要定义你的数据实体类。这个类通常会包含一些属性和方法。

class User {
  final String id;
  final String name;
  final int age;

  User({required this.id, required this.name, required this.age});

  // 实现 toString 方法方便调试
  @override
  String toString() => 'User(id: $id, name: $name, age: $age)';
}

2. 创建实体存储

接下来,你可以创建一个 EntityStore 实例来管理 User 实体。

import 'package:entity_store/entity_store.dart';

final entityStore = EntityStore<String, User>();

这里的 String 是实体的唯一标识符类型,User 是实体类型。

3. 添加实体

你可以使用 add 方法将实体添加到存储中。

final user = User(id: '1', name: 'Alice', age: 25);
entityStore.add(user);

4. 获取实体

你可以通过 get 方法获取单个实体,或者通过 getAll 方法获取所有实体。

final user = entityStore.get('1');
print(user); // User(id: 1, name: Alice, age: 25)

final allUsers = entityStore.getAll();
print(allUsers); // {1: User(id: 1, name: Alice, age: 25)}

5. 更新实体

你可以使用 update 方法来更新实体。

entityStore.update(User(id: '1', name: 'Alice', age: 26));

6. 删除实体

你可以使用 remove 方法来删除实体。

entityStore.remove('1');

7. 监听数据变化

entity_store 支持监听实体的变化。你可以使用 stream 来监听特定实体的变化,或者使用 streamAll 来监听所有实体的变化。

entityStore.stream('1').listen((user) {
  print('User updated: $user');
});

entityStore.streamAll().listen((users) {
  print('All users: $users');
});

高级用法

1. 自定义存储

你可以通过继承 EntityStore 来创建自定义的存储类,以便在其中添加更多的业务逻辑。

class UserStore extends EntityStore<String, User> {
  // 添加自定义方法或重写现有方法
}
回到顶部