Flutter通用用例插件generic_usecase的使用

Flutter通用用例插件 generic_usecase 的使用

简介

在使用Clean Architecture的过程中,我们经常发现自己重复编写用例的通用代码。generic_usecase 插件提供了一个小组件,用于封装应用程序的场景逻辑。通过这些类,你可以将你的逻辑封装在一个原子元素中,并在整个应用程序中注入和使用。

特性

  • ✅ 简单易用的API
  • ✅ 完全测试(100%覆盖率)
  • ✅ 完全文档化

可用的用例类型:

  • Usecase<Input, Output>
  • NoParamsUsecase<Output>
  • StreamUsecase<Input, Output>
  • NoParamsStreamUsecase<Output>

使用示例

简单用例

假设你想将两个数字相加,可以创建一个用例如下:

class AdditionUsecase extends Usecase<int, int> {
  const AdditionUsecase();

  [@override](/user/override)
  FutureOr<int> execute(int params) async => params + params;
}

void main() async {
  final addition = AdditionUsecase();
  await addition(2).then(print, onError: print); // 输出:4
}

execute 方法会在调用用例的 call 方法时执行。

使用流用例

你可以使用流用例返回一个 Stream 而不是原始值:

class GeneratorUsecase extends NoParamsStreamUsecase<int> {
  const GeneratorUsecase();

  [@override](/user/override)
  Stream<int> execute() async* {
    for (int i = 0; i < 10; i++) {
      await Future<void>.delayed(const Duration(seconds: 1));
      yield i;
    }
  }
}

void main() {
  final generator = GeneratorUsecase();
  final stream = generator();

  stream.listen(
    print,
    onError: print,
    onDone: () => print('Done'),
  );
}

检查前提条件和后置条件

你可以在用例中添加前提条件检查,它将在 execute 方法之前执行:

class DivisionUsecase extends Usecase<(int, int), double> {
  const DivisionUsecase();

  [@override](/user/override)
  FutureOr<ConditionsResult> checkPreconditions((int, int)? params) {
    if (params == null) {
      return ConditionsResult(isValid: false, message: 'Params is null');
    }

    if (params.$2 == 0) {
      return ConditionsResult(isValid: false, message: 'Cannot divide by 0');
    }

    return ConditionsResult(isValid: true);
  }

  [@override](/user/override)
  FutureOr<double> execute((int, int) params) async => params.$1 / params.$2;
}

你也可以添加后置条件检查,在 execute 方法之后执行:

class AdditionUsecase extends Usecase<int, int> {
  const AdditionUsecase();

  [@override](/user/override)
  FutureOr<int> execute(int params) async => params + params;

  [@override](/user/override)
  FutureOr<ConditionsResult> checkPostconditions(int? result) {
    if (result == null) {
      return ConditionsResult(isValid: false, message: 'Result is null');
    }

    if (result < 0) {
      return ConditionsResult(isValid: false, message: 'Result is negative');
    }

    return ConditionsResult(isValid: true);
  }
}

捕获异常

你可以通过覆盖 onException 方法来捕获由用例抛出的异常:

class AdditionUsecase extends Usecase<int, int> {
  const AdditionUsecase();

  [@override](/user/override)
  FutureOr<int> execute(int params) async => params + params;

  [@override](/user/override)
  FutureOr<int> onException(Object e) {
    print(e); // 打印异常信息
    return super.onException(e);
  }
}

这个方法会在 execute 方法抛出异常时被调用,也会在前提条件或后置条件检查失败时被调用。

使用 Result 对象

通过组合前面的例子,你可以创建一个返回 Result 对象的用例。通过捕获异常和检查前提条件与后置条件,你可以返回一个 Result 对象,该对象要么是 Success,要么是 Failure

class DivisionResultUsecase extends Usecase<(int, int), Result<double, Failure>> {
  const DivisionResultUsecase();

  [@override](/user/override)
  FutureOr<ConditionsResult> checkPreconditions((int, int)? params) {
    if (params == null) {
      return ConditionsResult(isValid: false, message: 'Params is null');
    }

    if (params.$2 == 0) {
      return ConditionsResult(isValid: false, message: 'Cannot divide by 0');
    }

    return ConditionsResult(isValid: true);
  }

  [@override](/user/override)
  FutureOr<Result<double, Failure>> execute((int, int) params) async =>
      Result.success(params.$1 / params.$2);

  [@override](/user/override)
  FutureOr<Result<double, Failure>> onException(Object e) {
    if (e case UsecaseException _) {
      return Result.failure(Failure(e.message ?? ''));
    }
    if (e case Exception || Error) {
      return Result.failure(Failure(e.toString()));
    }
    return Result.failure(Failure(''));
  }
}

示例代码

下面是一个完整的示例代码,展示了如何使用 generic_usecase 插件:

import 'dart:async';
import 'package:generic_usecase/generic_usecase.dart';

Future<void> main() async {
  print('AdditionUsecase');

  const AdditionUsecase addition = AdditionUsecase();

  print('> addition(2)');
  await display(() => addition(2));

  print('> addition(null)');
  await display(() => addition(null));

  print('> addition(-2)');
  await display(() => addition(-2));

  print('DivisionUsecase');

  const DivisionUsecase division = DivisionUsecase();

  print('> division(null)');
  await display(() => division(null));

  print('> division((2, 0))');
  await display(() => division((2, 0)));

  print('> division((4, 2))');
  await display(() => division((4, 2)));

  print('GeneratorUsecase');

  const GeneratorUsecase generator = GeneratorUsecase();

  print('> generator()');
  generator().listen(
    print,
    onError: print,
    onDone: () => print('Done'),
  );
}

class AdditionUsecase extends Usecase<int, int> {
  const AdditionUsecase();

  [@override](/user/override)
  FutureOr<int> execute(int params) async => params + params;

  [@override](/user/override)
  FutureOr<ConditionsResult> checkPostconditions(int? result) {
    if (result == null) {
      return ConditionsResult(isValid: false, message: 'Result is null');
    }

    if (result < 0) {
      return ConditionsResult(isValid: false, message: 'Result is negative');
    }

    return ConditionsResult(isValid: true);
  }
}

class DivisionUsecase extends Usecase<(int, int), double> {
  const DivisionUsecase();

  [@override](/user/override)
  FutureOr<ConditionsResult> checkPreconditions((int, int)? params) {
    if (params == null) {
      return ConditionsResult(isValid: false, message: 'Params is null');
    }

    if (params.$2 == 0) {
      return ConditionsResult(isValid: false, message: 'Cannot divide by 0');
    }

    return ConditionsResult(isValid: true);
  }

  [@override](/user/override)
  FutureOr<double> execute((int, int) params) async => params.$1 / params.$2;
}

class GeneratorUsecase extends NoParamsStreamUsecase<int> {
  const GeneratorUsecase();

  [@override](/user/override)
  Stream<int> execute() async* {
    for (int i = 0; i < 5; i++) {
      await Future<void>.delayed(const Duration(seconds: 1));
      yield i;
    }
  }
}

更多关于Flutter通用用例插件generic_usecase的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter通用用例插件generic_usecase的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用generic_usecase插件的一个示例。这个插件通常用于实现业务逻辑层的通用用例模式,使得代码更加模块化和可测试。

首先,确保你已经在pubspec.yaml文件中添加了generic_usecase依赖:

dependencies:
  flutter:
    sdk: flutter
  generic_usecase: ^最新版本号  # 请替换为实际的最新版本号

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

定义用例和数据模型

假设我们有一个简单的用户数据模型和一个获取用户信息的用例。

用户数据模型

class User {
  final String id;
  final String name;

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

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as String,
      name: json['name'] as String,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
    };
  }
}

定义获取用户信息的用例

我们需要定义一个获取用户信息的用例。这通常包括一个输入(请求参数)和一个输出(响应数据或错误)。

import 'package:dartz/dartz.dart';
import 'package:generic_usecase/generic_usecase.dart';

class GetUserInput {
  final String userId;

  GetUserInput({required this.userId});
}

class GetUserOutput {
  final Either<String, User> result;

  GetUserOutput({required this.result});
}

class GetUserUseCase implements UseCase<GetUserOutput, GetUserInput> {
  final UserRepository userRepository;

  GetUserUseCase({required this.userRepository});

  @override
  Future<GetUserOutput> execute(GetUserInput input) async {
    try {
      User user = await userRepository.getUser(input.userId);
      return GetUserOutput(result: Right(user));
    } catch (e) {
      return GetUserOutput(result: Left(e.toString()));
    }
  }
}

定义数据仓库

数据仓库通常用于处理数据的获取和存储。在这个例子中,我们假设有一个UserRepository

import 'dart:convert';
import 'package:http/http.dart' as http;

class UserRepository {
  final String apiUrl = 'https://api.example.com/users/';

  Future<User> getUser(String userId) async {
    final response = await http.get(Uri.parse('$apiUrl$userId'));

    if (response.statusCode == 200) {
      Map<String, dynamic> body = jsonDecode(response.body);
      return User.fromJson(body);
    } else {
      throw Exception('Failed to load user');
    }
  }
}

使用用例

现在,我们可以在Flutter组件中使用这个用例来获取用户信息。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: UserProfilePage(),
    );
  }
}

class UserProfilePage extends StatefulWidget {
  @override
  _UserProfilePageState createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  late GetUserUseCase _getUserUseCase;
  late Future<GetUserOutput> _futureUserOutput;

  @override
  void initState() {
    super.initState();
    UserRepository userRepository = UserRepository();
    _getUserUseCase = GetUserUseCase(userRepository: userRepository);
    _futureUserOutput = _getUserUseCase.execute(GetUserInput(userId: '1'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('User Profile'),
      ),
      body: FutureBuilder<GetUserOutput>(
        future: _futureUserOutput,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            GetUserOutput output = snapshot.data!;
            return output.result.fold(
              (error) => Text('Error: $error'),
              (user) => Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Text('ID: ${user.id}'),
                  Text('Name: ${user.name}'),
                ],
              ),
            );
          } else {
            return CircularProgressIndicator();
          }
        },
      ),
    );
  }
}

这个示例展示了如何使用generic_usecase插件来定义和执行一个获取用户信息的用例。你可以根据自己的需求进一步扩展和修改这个示例。

回到顶部