Flutter数据封装插件sealed_result的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter数据封装插件sealed_result的使用

简介

sealed_result 是一个用于处理 Dart 和 Flutter 中错误的库,它实现了 Result Monad 的简单实现。Result Monad 是一种以函数式方式处理错误的方法,通过包装一个值,该值可以是成功或失败的结果。这种方式避免了抛出异常,并以更明确的方式处理错误。

功能

  • 创建 Result

    • Result.ok(value):创建一个成功的 Result。
    • Ok(value):与 Result.ok(value) 相同。
    • Result.success(value):与 Result.ok(value) 相同。
    • Result.err(error, [stackTrace]):创建一个失败的 Result。
    • Err(error, [stackTrace]):与 Result.err(error, [stackTrace]) 相同。
    • Result.error(error, [stackTrace]):与 Result.err(error, [stackTrace]) 相同。
    • Result.failure(error, [stackTrace]):与 Result.err(error, [stackTrace]) 相同。
    • Result.from(() => value):从可能抛出异常的函数创建 Result。
    • Result.fromAsync(() => future):从可能失败的 Future 创建 Result。
    • Result.fromCondition({condition, value, error}):从条件创建 Result。
    • Result.fromConditionLazy({condition: () => condition, value: () => value, error: () => error}):从带有惰性求值的条件创建 Result。
  • 提取 Result 中的值

    • result.ok:如果 Result 成功,则获取值;否则丢弃错误。
    • result.err:如果 Result 失败,则获取错误;否则丢弃值。
    • result.stackTrace:如果 Result 失败,则获取堆栈跟踪。
    • result.expect():如果 Result 成功,则获取值;否则抛出异常。
    • result.expectErr():如果 Result 失败,则获取错误;否则抛出异常。
    • result.unwrap():与 result.expect() 相同。
    • result.unwrapErr():与 result.expectErr() 相同。
    • result.unwrapOr(defaultValue):如果 Result 成功,则获取值;否则返回默认值。
    • result.unwrapOrElse((error) => defaultValue):如果 Result 成功,则获取值;否则返回默认值(惰性求值)。
  • 检查 Result 的值

    • result.isOk:检查 Result 是否成功。
    • result.isErr:检查 Result 是否失败。
    • result.contains(value):检查 Result 是否包含特定值。
    • result.containsErr(error):检查 Result 是否包含特定错误。
    • result.containsLazy(() => value):检查 Result 是否包含特定值(惰性求值)。
    • result.containsErrLazy(() => error):检查 Result 是否包含特定错误(惰性求值)。
    • result.inspect((value) => void):检查 Result 的值。
    • result.inspectErr((error) => void):检查 Result 的错误。
    • result1 == result2:检查两个 Result 是否相等。
    • result1 != result2:检查两个 Result 是否不相等。
  • 转换 Result

    • result.map<U>(U Function(value) transform):转换 Result 的值。
    • result.mapAsync<U>(FutureOr<U> Function(value) transform):异步转换 Result 的值。
    • result.mapErr<U>(U Function(error) transform):转换 Result 的错误。
    • result.mapErrAsync<U>(FutureOr<U> Function(error) transform):异步转换 Result 的错误。
    • result.mapOr<U>(U defaultValue, U Function(value) transform):转换 Result 的值或返回默认值。
    • result.mapOrAsync<U>(FutureOr<U> defaultValue, FutureOr<U> Function(value) transform):异步转换 Result 的值或返回默认值。
    • result.mapOrElse<U>(U Function(value) defaultFn, U Function(error) transform):转换 Result 的值和错误。
    • result.mapOrElseAsync<U>(FutureOr<U> Function(value) defaultFn, FutureOr<U> Function(error) transform):异步转换 Result 的值和错误。
    • result.fold<U>(U Function(value) okFn, U Function(error) errFn):与 mapOrElse 相同,但语法不同。
    • result.foldAsync<U>(FutureOr<U> Function(value) okFn, FutureOr<U> Function(error) errFn):与 mapOrElseAsync 相同,但语法不同。
    • result.flatten():将 Result<Result<T,E>,E> 展平为 Result<T,E>
    • result.and<U>(Result<U,E> other):组合两个 Result。
    • result.andThen<U>(Result<U,E> Function(value) transform):使用函数组合两个 Result。
    • result.or<F>(Result<T,F> other):组合两个 Result。
    • result.orElse<F>(Result<T,F> Function(error) transform):使用函数组合两个 Result。
    • result1 & result2:使用 and 操作符组合两个 Result。
    • result1 | result2:使用 or 操作符组合两个 Result。

使用示例

导入包

import 'package:sealed_result/sealed_result.dart';

创建一个可能失败的函数

enum Version { version1, version2 }

Result<Version, ResultException> parseVersion(List<int> header) =>
  switch (header) {
    final header when header.isEmpty =>
      const Result.err(ResultException('invalid header length')),
    final header when header[0] == 1 => const Result.ok(Version.version1),
    final header when header[0] == 2 => const Result.ok(Version.version2),
    _ => const Result.err(ResultException('invalid version')),
  };

使用该函数

final version = parseVersion([1, 2, 3, 4]);
print(
  switch (version) {
    Ok(ok: final value) => 'working with version: $value',
    Err(err: final error) => 'error parsing header: $error',
  },
);

如果 version = parseVersion([1, 2, 3, 4]),则输出将是:

working with version: Version.version1

如果 version = parseVersion([3, 2, 3, 4]),则输出将是:

error parsing header: ResultException: invalid version

如果 version = parseVersion([]),则输出将是:

error parsing header: ResultException: invalid header length

更多示例

示例 1:基本用法
static Future<void> example1(List<String> args) async {
  Result<Version, ResultException> parseVersion(List<int> header) =>
      switch (header) {
        final header when header.isEmpty =>
          const Result.err(ResultException('invalid header length')),
        final header when header[0] == 1 => const Result.ok(Version.version1),
        final header when header[0] == 2 => const Result.ok(Version.version2),
        _ => const Result.err(ResultException('invalid version')),
      };

  final version = parseVersion([1, 2, 3, 4]);
  print(
    switch (version) {
      Ok(ok: final value) => 'working with version: $value',
      Err(err: final error) => 'error parsing header: $error',
    },
  );
}
示例 2:检查和转换 Result
static Future<void> example2(List<String> args) async {
  const goodResult = Ok<int, int>(10);
  const badResult = Err<int, int>(10);

  // The `is_ok` and `is_err` methods do what they say.
  assert(goodResult.isOk && !goodResult.isErr, 'goodResult is not ok');
  assert(badResult.isErr && !badResult.isOk, 'badResult is not err');

  // `map` consumes the `Result` and produces another.
  final goodResult2 = goodResult.map((i) => i + 1);
  final badResult2 = badResult.map((i) => i - 1);

  // Use `and_then` to continue the computation.
  final goodResult3 = goodResult2.andThen((i) => Result.ok(i == 11));

  // Use `or_else` to handle the error.
  final badResult3 = badResult2.orElse((i) => Result<int, int>.ok(i + 20));

  print(badResult3);

  // Consume the result and return the contents with `unwrap`.
  final finalAwesomeResult = goodResult3.unwrap();

  print(finalAwesomeResult);
}
示例 3:使用 Future
static Future<void> example3(List<String> args) async {
  final goodResult = Result.fromAsync<int, int>(() => Future.value(10));
  final badResult = Result.fromAsync<int, int>(() => Future.error(10));

  // The `is_ok` and `is_err` methods do what they say.
  assert(
    await goodResult.isOk && !await goodResult.isErr,
    'goodResult is not ok',
  );
  assert(
    await badResult.isErr && !await badResult.isOk,
    'badResult is not err',
  );

  // `map` consumes the `Result` and produces another.
  final goodResult2 = goodResult.map((i) => i + 1);
  final badResult2 = badResult.map((i) => i - 1);

  // Use `and_then` to continue the computation.
  final goodResult3 = goodResult2.andThen((i) => Result.ok(i == 11));

  // Use `or_else` to handle the error.
  final badResult3 = badResult2.orElse((i) => Result<int, int>.ok(i + 20));

  print(await badResult3);

  // Consume the result and return the contents with `unwrap`.
  final finalAwesomeResult = await goodResult3.unwrap();

  print(finalAwesomeResult);
}

注意事项

  • Result 是一个密封类,因此可以使用 switch 进行穷尽匹配。如果只想获取 Result 的值,建议使用 Dart 3 的模式匹配而不是 fold 方法。
  • 所有 Result 方法都可以与 Future 一起使用。

完整示例 Demo

以下是一个完整的 Flutter 应用程序示例,展示了如何在 Flutter 中使用 sealed_result 插件:

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

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sealed Result Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Sealed Result Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  [@override](/user/override)
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _resultText = '';

  void _parseVersion(List<int> header) {
    Result<Version, ResultException> parseVersion(List<int> header) =>
        switch (header) {
          final header when header.isEmpty =>
            const Result.err(ResultException('invalid header length')),
          final header when header[0] == 1 => const Result.ok(Version.version1),
          final header when header[0] == 2 => const Result.ok(Version.version2),
          _ => const Result.err(ResultException('invalid version')),
        };

    final version = parseVersion(header);
    setState(() {
      _resultText = switch (version) {
        Ok(ok: final value) => 'Working with version: $value',
        Err(err: final error) => 'Error parsing header: $error',
      };
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '$_resultText',
              style: Theme.of(context).textTheme.headline4,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => _parseVersion([1, 2, 3, 4]),
              child: Text('Parse Version 1'),
            ),
            ElevatedButton(
              onPressed: () => _parseVersion([3, 2, 3, 4]),
              child: Text('Parse Version 2'),
            ),
            ElevatedButton(
              onPressed: () => _parseVersion([]),
              child: Text('Parse Empty Header'),
            ),
          ],
        ),
      ),
    );
  }
}

enum Version { version1, version2 }

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

1 回复

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


当然,以下是如何在Flutter项目中使用sealed_result插件进行数据封装的示例代码。sealed_result插件通常用于封装API调用或其他异步操作的结果,使得结果处理更加类型安全和清晰。

步骤 1: 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  sealed_result: ^x.y.z  # 替换为最新版本号

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

步骤 2: 定义数据封装类

使用sealed_result定义一个封装类,通常包括成功和失败两种情况。假设我们有一个API调用,返回用户信息或错误信息。

import 'package:sealed_result/sealed_result.dart';

// 定义成功类型
class UserSuccess with Success<User> {
  UserSuccess(User data) : super(data);
}

// 定义失败类型
class UserFailure with Failure {
  final String message;

  UserFailure(this.message);

  @override
  String? getErrorMessage() => message;
}

// 用户信息类
class User {
  final String id;
  final String name;
  final String email;

  User(this.id, this.name, this.email);
}

// 定义一个SealedResult类型别名
typealias UserResult = SealedResult<UserSuccess, UserFailure>;

步骤 3: 模拟API调用

接下来,我们模拟一个API调用,它返回一个UserResult

import 'dart:async';

Future<UserResult> fetchUser() async {
  // 模拟网络延迟
  await Future.delayed(Duration(seconds: 1));

  // 模拟成功结果
  // return SealedResult.success(UserSuccess(User('1', 'John Doe', 'john@example.com')));

  // 模拟失败结果
  return SealedResult.failure(UserFailure('Failed to fetch user data.'));
}

步骤 4: 使用封装后的数据

在你的UI组件中使用fetchUser函数,并根据结果更新UI。

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

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

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

class UserScreen extends StatefulWidget {
  @override
  _UserScreenState createState() => _UserScreenState();
}

class _UserScreenState extends State<UserScreen> {
  late final Completer<UserResult> _completer = Completer();

  @override
  void initState() {
    super.initState();
    fetchUser().then(_completer.complete);
    _completer.future.then(updateUI);
  }

  void updateUI(UserResult result) {
    if (result.isSuccess) {
      // 处理成功结果
      final user = result.data!.data;
      print('User ID: ${user.id}, Name: ${user.name}, Email: ${user.email}');
      // 更新UI,例如显示用户信息
    } else {
      // 处理失败结果
      final error = result.failure!.errorMessage!;
      print('Error: $error');
      // 更新UI,例如显示错误信息
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(error)),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User Info')),
      body: Center(child: CircularProgressIndicator()), // 显示加载指示器
    );
  }
}

在这个示例中,我们模拟了一个API调用,它返回一个UserResult。我们根据返回的结果更新UI,显示用户信息或错误信息。

这样,通过使用sealed_result插件,你可以更清晰地封装和处理异步操作的结果,使得代码更加健壮和易于维护。

回到顶部