Flutter错误处理插件errflow的使用

Flutter错误处理插件errflow的使用

动机

我创建了这个包,因为我发现处理异常非常困难:

  • 如果一个异常没有被捕获,应用程序会停止运行。
  • 有时不清楚某个异常是否已经在某处被捕获。
  • 不建议在不同的层捕获特定于原始层的异常(例如数据库错误或网络错误)。
  • 然而,从一个方法返回错误值(而不是异常本身以避免上述问题)给位于另一层的调用者一起与一些处理的结果是很困难的。

解决方案:

  • package:async 中的 <a href="https://pub.dev/documentation/async/latest/async/Result-class.html">Result</a>
  • package:errflow (本包)

这些看起来非常不同,但大致上,它们都提供了一种从方法传递结果和/或错误到调用者的方式;前者持有任意一个值,而后者允许调用者评估这两个值。

一个很大的区别是,本包还提供了处理程序和日志记录器,以便更轻松地统一处理错误。它使得错误处理可以集中化。

使用

初始化和清理

实例化 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow-class.html">ErrFlow</a>,默认值表示没有错误。该值被用作每个 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/scope.html">scope()</a> 的通知器的初始值。

当不再需要 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow-class.html">ErrFlow</a> 对象时,调用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/dispose.html">dispose()</a> 以确保对象持有的资源不会再次被使用。

enum CustomError {
  none,
  foo,
  bar,
}

...

final errFlow = ErrFlow<CustomError>(CustomError.none);

...

errFlow.dispose();

如果你更喜欢使用 <code>Exception</code> 及其子类型而不是自定义错误类型,指定 <code>Exception</code> 作为错误类型,并将 <code>null</code><code>none</code> 传递给构造函数以使用 <code>null</code> 作为默认值。

final errFlow = ErrFlow<Exception>();

设置/记录错误

传递给 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/scope.html">scope()</a> 回调函数的通知器具有 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/set.html">set()</a> 方法,用于更新错误值并调用日志记录器,以及 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/log.html">log()</a> 方法仅触发日志记录器。

  1. 使用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/scope.html">scope()</a><a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier-class.html">ErrNotifier</a> 传递给可能引发错误的函数。
  2. 在函数中发生错误时,调用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/set.html">set()</a>
  3. 监听器会收到错误并将其存储为最后一个错误 (<a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/lastError.html">lastError</a>),以便稍后在函数内部检查。
  4. 监听器还会调用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/logger.html">logger</a> 来记录有关错误的信息,如果通过 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/set.html">set()</a><a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/log.html">log()</a> 提供的话。
  5. 如果满足条件,函数完成时会调用相关的错误处理程序。

错误处理程序将在文档后面详细解释。

final result = await errFlow.scope<bool>(
  (notifier) => yourFunc(notifier),
  ...
);
Future<bool> yourFunc(ErrNotifier notifier) async {
  try {
    await errorProneProcess();
  } catch (e, s) {
    // 这更新了最后一个错误值并触发了日志记录器。
    notifier.set(CustomError.foo, e, s, 'additional info');
  }

  // 如果有必要,你可以使用 hasError 检查是否设置了某些错误。
  if (notifier.hasError) {
    ...
    return false;
  }

  return true;
}

你也可以只使用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/set.html">set()</a> 的第一个参数来不触发日志记录器:

notifier.set(CustomError.foo);

或者使用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/log.html">log()</a> 仅进行日志记录:

notifier.log(e, s, 'additional info');

问:是不是不方便要传递一个通知器?

虽然可以去除传递 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier-class.html">ErrNotifier</a> 对象的麻烦,但我选择不这样做,因为带有 <code>ErrNotifier</code> 类型参数的方法签名可以帮助你识别哪些方法需要错误处理。

处理错误

<a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/scope.html">scope()</a> 执行一个函数,并根据 <code>errorIf</code><code>criticalIf</code> 指定的条件在函数结束时处理其中发生的错误。使用两者或其中之一来设置函数结果是否被视为非关键/关键错误。<code>criticalIf</code> 的条件在 <code>errorIf</code> 之前进行评估。

如果满足任一条件,则调用相应的处理程序 <code>onError</code><code>onCriticalError</code>。在这些处理程序中进行一些错误处理,如根据错误的严重程度显示不同的消息。

final result = await errFlow.scope<bool>(
  (notifier) => yourMethod(notifier),

  // 使用两者或仅其中一个 errorIf 和 criticalIf。
  errorIf: (result, error) => error == CustomError.foo,
  criticalIf: (result, error) => error == CustomError.bar,

  // 有一种方法可以在每次编写时避免编写 onError 和 onCriticalError,
  // 这将在后面解释。
  onError: (result, error) => _onError(result, error),
  onCriticalError: (result, error) => _onCriticalError(result, error),
);

处理程序函数接收函数结果和错误值,这意味着你可以结合它们来调整触发处理程序的条件。

例如:如果设置了任何错误,则触发 <code>onError</code> 处理程序:

errorIf: (result, error) => error != errFlow.defaultError

例如:如果进程因连接错误以外的原因失败,则触发 <code>onError</code> 处理程序:

errorIf: (result, error) => !result && error != CustomError.connection

手动处理错误

<a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/combiningScope.html">combiningScope()</a> 在你想手动检查和处理错误,只让 ErrFlow 进行日志记录时非常有用。

它基本上类似于 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/loggingScope.html">loggingScope()</a>(稍后描述),但返回 <a href="https://pub.dev/documentation/errflow/latest/errflow/CombinedResult-class.html">CombinedResult</a>,该结果包含函数结果和错误。使用组合结果,可以在作用域完成后处理错误。

final result = await errFlow.combiningScope(
  (notifier) => yourMethod(notifier),
);

if (result.hasError) {
  switch (result.error!) {
    case AppError.init:
      print('[ERROR] Initialization failed.');
  }
}

<code>CombinedResult</code> 类似于 package:async 中的 <code>Result</code>,但有一些明显的差异:

  • 它有值和错误,而 <code>Result</code> 只有一个。
  • 它始终有一个值,无论是否有错误。
  • 如果没有错误,默认错误值设置为 <code>error</code>
    • 具有非空 <code>error</code> 值并不总是意味着发生了错误。

忽略错误

如果一个方法,可以在你的代码的不同地方调用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/set.html">set()</a>,你可能想在某些地方显示错误信息,但在其他地方忽略它。这可以通过使用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/loggingScope.html">loggingScope()</a><a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/ignorableScope.html">ignorableScope()</a> 来实现,允许你仅记录错误而不处理它们,或者完全忽略它们。

loggingScope()

<a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/loggingScope.html">loggingScope()</a> 传递的 <code>notifier</code><a href="https://pub.dev/documentation/errflow/latest/errflow/LoggingErrNotifer-class.html">LoggingErrNotifier</a> 的对象。在该对象上调用 <a href="https://pub.dev/documentation/errflow/latest/errflow/LoggingErrNotifier/set.html">set()</a> 仅更新 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/lastError.html">lastError</a> 并触发日志记录器(以及添加的监听函数),而不触发错误处理程序。

final result = await errFlow.loggingScope<bool>(
  (LoggingErrNotifier notifier) => yourMethod(notifier),
);

bool yourMethod(ErrNotifier notifier) {
  try {
    return ...;
  } catch (e, s) {
    notifier.set(CustomError.foo, e, s);  // 仅更新 lastError 并记录错误。
    return false;
  }
}

ignorableScope()

<a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/ignorableScope.html">ignorableScope()</a> 传递的 <code>notifier</code><a href="https://pub.dev/documentation/errflow/latest/errflow/IgnorableErrNotifier-class.html">IgnorableErrNotifier</a> 的对象。在该对象上调用 <a href="https://pub.dev/documentation/errflow/latest/errflow/IgnorableErrNotifier/set.html">set()</a><a href="https://pub.dev/documentation/errflow/latest/errflow/IgnorableErrNotifier/log.html">log()</a> 不会触发错误处理程序或日志记录器。<a href="https://pub.dev/documentation/errflow/latest/errflow/IgnorableErrNotifier/set.html">set()</a> 仅更新 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/lastError.html">lastError</a>

final result = await errFlow.ignorableScope<bool>(
  (IgnorableErrNotifier notifier) => yourMethod(notifier),
);

bool yourMethod(ErrNotifier notifier) {
  try {
    return ...;
  } catch (e, s) {
    notifier.set(CustomError.foo, e, s);  // 仅更新 lastError。
    return false;
  }
}

默认错误处理程序

你可能希望一致地使用特定的处理程序来处理非关键错误,以及相同的或另一个处理程序来处理关键错误。在这种情况下,<code>errorHandler</code><code>criticalErrorHandler</code> 将很有用。你可以提前指定如何处理错误,并在每个 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/scope.html">scope()</a> 中省略 <code>onError</code><code>onCriticalError</code>

void _errorHandler<T>(T result, CustomError? error) {
  switch (error) {
    case CustomError.foo:
      // 处理 foo 错误(例如显示错误详情)
      break;
    default:
      // 处理其他错误
      break;
  }
}

...

errFlow
  ..errorHandler = _errorHandler
  ..criticalErrorHandler = _errorHandler;

final result = await errFlow.scope<bool>(
  (notifier) => yourMethod(notifier),
  errorIf: (result, error) => !result,
);

日志记录器

要使用默认日志记录器(简单地将信息打印到控制台),在第一次日志记录之前调用 <a href="https://pub.dev/documentation/errflow/latest/errflow/ErrFlow/useDefaultLogger.html">useDefaultLogger()</a>

errFlow.useDefaultLogger();

如果它缺少你需要的功能,设置自己的日志记录器。

// 返回类型可以是 Future 或非 Future。
// 注意:即使返回 Future,set() 和 log() 也不会等待它。
void _logger(Object e, StackTrace? s, {Object? reason}) {
  // 日志记录操作
}

...

errFlow.logger = _logger;

在 Flutter 中,<code>recordError()</code> 方法可以分配给 firebase_crashlytics 包的日志记录器。

import 'package:firebase_crashlytics/firebase_crashlytics.dart';

...

errFlow.logger = FirebaseCrashlytics.recordError;

务必设置默认或自定义日志记录器,否则在调试模式下会发生断言错误。

添加/移除监听器

通常不需要,但你可以添加自定义监听器以满足特殊需求。

void _listener({CustomError? error, Object? exception, StackTrace? stack, Object? context}) {
  // 某些处理
}

...

errFlow.addListener(_listener);

...

errFlow.removeListener(_listener);

更多关于Flutter错误处理插件errflow的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

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


errflow 是一个用于 Flutter 的错误处理插件,它可以帮助你更优雅地捕获和处理应用程序中的错误。它特别适用于处理异步操作中的错误,并提供了简洁的 API 来管理错误流。以下是如何使用 errflow 插件的基本指南。

1. 安装插件

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

dependencies:
  flutter:
    sdk: flutter
  errflow: ^1.0.0  # 使用最新版本

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

2. 导入包

在需要使用 errflow 的文件中导入包:

import 'package:errflow/errflow.dart';

3. 初始化 errFlow

在你的类或 Widget 中初始化 errFlow

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final errFlow = ErrFlow();
  
  @override
  void dispose() {
    errFlow.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ErrFlow Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: _performAsyncOperation,
          child: Text('Perform Async Operation'),
        ),
      ),
    );
  }
  
  Future<void> _performAsyncOperation() async {
    errFlow.scope((notifier) async {
      try {
        // 模拟一个异步操作
        await Future.delayed(Duration(seconds: 2));
        throw Exception('Something went wrong!');
      } catch (e) {
        // 捕获错误并通知 errFlow
        notifier.set(e);
      }
    }, onError: (error) {
      // 处理错误
      print('Error occurred: $error');
    });
  }
}

4. 使用 errFlow.scope 处理错误

errFlow.scope 是一个用于捕获和处理错误的上下文的函数。它接受两个参数:

  • 一个异步函数,你可以在其中执行可能抛出异常的操作。
  • 一个 onError 回调,用于处理捕获到的错误。

scope 函数中,你可以使用 notifier.set(error) 来通知 errFlow 发生了错误。onError 回调会在错误发生时被调用,你可以在其中处理错误。

5. 处理多个错误

你可以在一个 scope 中处理多个错误,或者在不同的 scope 中处理不同的错误:

Future<void> _performMultipleOperations() async {
  errFlow.scope((notifier) async {
    try {
      await _operation1();
    } catch (e) {
      notifier.set(e);
    }
    
    try {
      await _operation2();
    } catch (e) {
      notifier.set(e);
    }
  }, onError: (error) {
    print('Error occurred: $error');
  });
}

6. 全局错误处理

你还可以使用 errFlow 来设置全局错误处理器:

void main() {
  final errFlow = ErrFlow();
  
  errFlow.setGlobalHandler((error) {
    print('Global error handler: $error');
  });
  
  runApp(MyApp());
}

7. 使用 ErrFlowNotifier 进行更细粒度的控制

ErrFlowNotifier 可以用于在 scope 内部进行更细粒度的错误控制:

Future<void> _performAsyncOperation() async {
  errFlow.scope((notifier) async {
    if (someCondition) {
      notifier.set(Exception('Condition not met'));
      return;
    }
    
    try {
      await _operation();
    } catch (e) {
      notifier.set(e);
    }
  }, onError: (error) {
    print('Error occurred: $error');
  });
}

8. 处理不同的错误类型

你可以根据错误类型进行不同的处理:

Future<void> _performAsyncOperation() async {
  errFlow.scope((notifier) async {
    try {
      await _operation();
    } on NetworkException catch (e) {
      notifier.set(e);
    } on DatabaseException catch (e) {
      notifier.set(e);
    } catch (e) {
      notifier.set(e);
    }
  }, onError: (error) {
    if (error is NetworkException) {
      print('Network error: $error');
    } else if (error is DatabaseException) {
      print('Database error: $error');
    } else {
      print('Unknown error: $error');
    }
  });
}

9. 使用 ErrFlow 进行状态管理

errFlow 还可以与状态管理结合使用,例如在 ProviderRiverpod 中:

class MyProvider with ChangeNotifier {
  final errFlow = ErrFlow();
  
  Future<void> performAsyncOperation() async {
    errFlow.scope((notifier) async {
      try {
        await _operation();
      } catch (e) {
        notifier.set(e);
      }
    }, onError: (error) {
      print('Error occurred: $error');
      notifyListeners();
    });
  }
}
回到顶部