Flutter CQRS架构与Cubit工具插件leancode_cubit_utils_cqrs的使用

Flutter CQRS架构与Cubit工具插件leancode_cubit_utils_cqrs的使用

要求

  1. 参考提供的内容和示例代码回答此问题。
  2. 提供完整的示例Demo。
  3. 不添加无用图片。
  4. 将英文介绍转换为简体中文。
  5. 保留代码不进行翻译。
  6. 添加必要的注释。

需求

cqrs: ">=10.0.1"

安装

在项目中添加依赖:

flutter pub add leancode_cubit_utils_cqrs

导入包:

import 'package:leancode_cubit_utils_cqrs/leancode_cubit_utils_cqrs.dart';

使用

leancode_cubit_utils_cqrs 包包含了一个完整的实现,用于处理CQRS查询的cubits。这些实现适用于单个请求工具和分页工具。

单个请求工具

QueryCubit

QueryCubit 用于执行单个CQRS查询。以下是 QueryCubit 的示例实现:

class ProjectDetailsCubit extends QueryCubit<ProjectDetailsDTO, ProjectDetailsDTO> {
  ProjectDetailsCubit({
    required this.cqrs,
    required this.id,
  }) : super('ProjectDetailsCubit');

  final Cqrs cqrs;
  final String id;

  [@override](/user/override)
  // 将给定的TRes映射到TOut。在这种情况下,我们不想改变它,所以直接返回数据。
  ProjectDetailsDTO map(ProjectDetailsDTO data) => data;

  [@override](/user/override)
  // 在此方法中执行查询并以QueryResult<TRes>的形式返回结果。QueryResult<TRes>由QueryCubit内部处理。
  Future<QueryResult<ProjectDetailsDTO>> request() {
    return cqrs.get(ProjectDetails(id: id));
  }
}

ArgsQueryCubit

ArgsQueryCubit<TArgs, TRes, TOut> 是一个接受参数的 QueryCubit 版本。TArgs 确定了请求方法接受的参数类型。TResTOut 的作用与 QueryCubit 相同。

使用QueryCubit和UseArgsQueryCubit

有时,没有必要对查询响应进行任何映射。这种情况下,没有必要实现扩展 QueryCubitArgsQueryCubit 的cubit。相反,可以使用提供的钩子之一 useQueryCubituseArgsQueryCubit。只需提供要执行的请求,然后就可以像通过 RequestCubitBuilder 传递它一样使用该cubit。

final queryCubit = useQueryCubit(
    () => cqrs.get(ProjectDetails(id: id)),
);

final argsQueryCubit = useArgsQueryCubit(
    (args) => cqrs.get(AllProjects(sortByNameDescending: args.isDescending)),
);

可以通过传递可选参数来配置 requestModeloggerTag。在 useQueryCubit 中,还可以通过传递 callOnCreate 标志来定义是否立即调用请求。

分页工具

分页工具是为了简化创建主元素为分页列表的页面而设计的。

PaginatedQueryCubit

PaginatedQueryCubit<TData, TRes, TItem> 是一种用于CQRS的分页cubit实现。它用于处理检索分页列表下一页的逻辑。它有三个泛型参数:

  • TData 表示我们希望存储和处理的附加数据。
  • TRes 表示从API返回项目的结构。
  • TItem 对应于计划在页面上显示的单个列表项模型(经过潜在转换)。

以下是 PaginatedQueryCubit 的示例实现:

class IdentitiesCubit extends PaginatedQueryCubit<void, PaginatedResult<KratosIdentityDTO>, KratosIdentityDTO> {
  IdentitiesCubit({
    super.config,
    required this.cqrs,
  }) : super(loggerTag: 'IdentitiesCubit');

  final Cqrs cqrs;

  [@override](/user/override)
  Future<QueryResult<PaginatedResult<KratosIdentityDTO>>> requestPage(
    PaginatedArgs args,
  ) {
    return cqrs.get(
      // 查询获取下一页
      SearchIdentities(
        pageSize: args.pageSize,
        pageNumber: args.pageNumber,
        emailPattern: args.searchQuery,
      ),
    );
  }

  [@override](/user/override)
  PaginatedResponse<void, KratosIdentityDTO> onPageResult(
    PaginatedResult<KratosIdentityDTO> page,
  ) {
    // 使用cubit方法计算是否有下一页
    final args = state.args;
    final hasNextPage = calculateHasNextPage(
      pageNumber: args.pageNumber,
      totalCount: page.totalCount,
    );

    // 返回带有下一页的响应
    return PaginatedResponse.append(
      items: page.items,
      hasNextPage: hasNextPage,
    );
  }
}

需要实现两个方法的主体:requestPageonPageResult。在第一个方法中执行请求并返回其结果。在第二个方法中,需要处理结果并以 PaginatedResponse 的形式返回它。

预请求

预请求允许你在请求第一页之前执行操作。例如,这可以用于获取可用过滤器。

QueryPreRequest

QueryPreRequest 是一个专为CQRS设计的预请求实现类。要利用此功能提供的预请求功能,创建一个扩展 QueryPreRequest 的类。

class FiltersPreRequest extends QueryPreRequest<List<Filter>, List<Filter>, User> {
  FiltersPreRequest({required this.cqrs});

  final Cqrs cqrs;

  [@override](/user/override)
  Future<QueryResult<List<Filter>>> request(PaginatedState<List<Filter>, User> state) {
    return api.getFilters();
  }

  [@override](/user/override)
  List<Filter> map(
    List<Filter> res,
    PaginatedState<List<Filter>, User> state,
  ) {
    return res;
  }
}

然后需要在 PaginatedCubit 构造函数中创建定义的 FiltersPreRequest 实例。

class IdentitiesCubit extends PaginatedQueryCubit<List<Filter>, PaginatedResult<KratosIdentityDTO>, KratosIdentityDTO> {
  IdentitiesCubit({
    super.config,
    preRequest: FiltersPreRequest(cqrs: cqrs), // 这里
    required this.cqrs,
  }) : super(loggerTag: 'IdentitiesCubit');

  /* IdentitiesCubit 的其余实现 */
}

示例

以下是一个完整的示例,展示了如何使用 leancode_cubit_utils_cqrs 包:

import 'dart:developer';

import 'package:cqrs/cqrs.dart';
import 'package:example/cqrs/cqrs.dart';
import 'package:example/pages/home_page.dart';
import 'package:example/pages/paginated/paginated_cubit_page.dart';
import 'package:example/pages/query/query_page.dart';
import 'package:flutter/material.dart';
import 'package:leancode_cubit_utils_cqrs/leancode_cubit_utils_cqrs.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';

class Routes {
  static const home = '/';
  static const simpleQuery = '/simple-query';
  static const simpleQueryHook = '/simple-query-hook';
  static const paginatedCubit = '/paginated-cubit';
}

void _setupLogger() {
  Logger.root.level = Level.ALL;

  Logger.root.onRecord.listen(
    (record) => log(
      record.message,
      time: record.time,
      sequenceNumber: record.sequenceNumber,
      level: record.level.value,
      name: record.loggerName,
      zone: record.zone,
      error: record.error,
      stackTrace: record.stackTrace,
    ),
  );

  FlutterError.onError = (details) {
    FlutterError.dumpErrorToConsole(details);
  };
}

void main() {
  final cqrs = createMockedCqrs();

  _setupLogger();

  runApp(
    Provider<Cqrs>.value(
      value: cqrs,
      child: PaginatedLayoutConfigProvider(
        onFirstPageLoading: (context) => const Center(
          child: CircularProgressIndicator(),
        ),
        onFirstPageError: (context, error, retry) => Error(
          retry: retry,
          error: error,
        ),
        onNextPageLoading: (context) => const Center(
          child: CircularProgressIndicator(),
        ),
        onNextPageError: (context, error, retry) => Error(
          retry: retry,
          error: error,
        ),
        onEmptyState: (context) => const Center(child: Text('No items')),
        child: RequestLayoutConfigProvider(
          requestMode: RequestMode.replace,
          onLoading: (BuildContext context) =>
              const CircularProgressIndicator(),
          onError: (
            BuildContext context,
            RequestErrorState<dynamic, dynamic> error,
            VoidCallback? onErrorCallback,
          ) =>
              const Text(
            'Error',
            style: TextStyle(color: Colors.red),
          ),
          child: const MainApp(),
        ),
      ),
    ),
  );
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) => MaterialApp(
        routes: <String, WidgetBuilder>{
          Routes.home: (_) => const HomePage(),
          Routes.simpleQuery: (_) => const QueryScreen(),
          Routes.simpleQueryHook: (_) => const QueryHookScreen(),
          Routes.paginatedCubit: (_) => const PaginatedCubitScreen(),
        },
      );
}

class Error extends StatelessWidget {
  const Error({
    super.key,
    this.retry,
    required this.error,
  });

  final VoidCallback? retry;
  final Object? error;

  [@override](/user/override)
  Widget build(BuildContext context) => retry != null
      ? Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(error.toString()),
            ElevatedButton(onPressed: retry, child: const Text('Retry')),
          ],
        )
      : Text(error.toString());
}

更多关于Flutter CQRS架构与Cubit工具插件leancode_cubit_utils_cqrs的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter CQRS架构与Cubit工具插件leancode_cubit_utils_cqrs的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter应用中实现CQRS(Command Query Responsibility Segregation,命令查询责任分离)架构,结合Cubit工具插件leancode_cubit_utils_cqrs,可以帮助我们更好地管理应用的状态和逻辑。以下是一个简单的代码案例,展示如何使用这些工具来构建一个基本的CQRS架构。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加leancode_cubit_utils_cqrs依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.0
  leancode_cubit_utils_cqrs: ^latest_version # 请替换为实际最新版本号

2. 定义命令和事件

在CQRS架构中,命令是用来触发状态改变的请求,而事件则是状态改变的结果。

// commands.dart
class CreateTodoCommand {
  final String title;
  final String description;

  CreateTodoCommand({required this.title, required this.description});
}

// events.dart
class TodoCreatedEvent {
  final String id;
  final String title;
  final String description;

  TodoCreatedEvent({required this.id, required this.title, required this.description});
}

3. 定义状态

状态表示应用当前的数据。

// states.dart
import 'package:flutter/material.dart';

class TodoState {
  final List<Todo> todos;

  TodoState({required this.todos});

  TodoState copyWith({List<Todo>? todos}) {
    return TodoState(
      todos: todos ?? this.todos,
    );
  }
}

class Todo {
  final String id;
  final String title;
  final String description;
  bool isCompleted;

  Todo({required this.id, required this.title, required this.description, this.isCompleted = false});
}

4. 定义Cubit和Repository

使用leancode_cubit_utils_cqrs中的工具来管理状态和命令处理。

// todo_cubit.dart
import 'package:bloc/bloc.dart';
import 'package:leancode_cubit_utils_cqrs/leancode_cubit_utils_cqrs.dart';
import 'commands.dart';
import 'events.dart';
import 'states.dart';

class TodoCubit extends CubitCqrsWithSimpleRepository<TodoState> {
  TodoCubit() : super(TodoState(todos: []));

  @override
  void onCommand(Command command) {
    if (command is CreateTodoCommand) {
      // 生成一个唯一的ID(实际应用中可能需要更复杂的逻辑)
      final id = DateTime.now().millisecondsSinceEpoch.toString();
      emitEvent(TodoCreatedEvent(id: id, title: command.title, description: command.description));
    }
  }

  @override
  TodoState applyEvent(TodoState state, Event event) {
    if (event is TodoCreatedEvent) {
      return state.copyWith(
        todos: [...state.todos, Todo(id: event.id, title: event.title, description: event.description)],
      );
    }
    return state;
  }
}

5. 使用Cubit

在UI中使用TodoCubit来管理状态和响应用户输入。

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'todo_cubit.dart';
import 'states.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Todo App')),
        body: BlocProvider<TodoCubit>(
          create: (_) => TodoCubit(),
          child: TodoList(),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            final context = BlocProvider.of<TodoCubit>(context);
            context.emitCommand(CreateTodoCommand(title: 'New Todo', description: 'Description'));
          },
          tooltip: 'Add',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

class TodoList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final todoCubit = BlocProvider.of<TodoCubit>(context);
    return BlocBuilder<TodoCubit, TodoState>(
      builder: (context, state) {
        return ListView.builder(
          itemCount: state.todos.length,
          itemBuilder: (_, index) {
            final todo = state.todos[index];
            return ListTile(
              title: Text(todo.title),
              subtitle: Text(todo.description),
              trailing: Checkbox(
                value: todo.isCompleted,
                onChanged: (value) {
                  // 这里可以添加逻辑来处理todo的完成状态改变,但在这个简单示例中忽略
                },
              ),
            );
          },
        );
      },
    );
  }
}

这个示例展示了如何使用leancode_cubit_utils_cqrs和Flutter的Bloc库来实现一个简单的CQRS架构。注意,这只是一个基本的实现,实际应用中可能需要更多的功能和更复杂的逻辑,如错误处理、持久化存储等。

回到顶部