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>
方法仅触发日志记录器。
- 使用
<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>
传递给可能引发错误的函数。 - 在函数中发生错误时,调用
<a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/set.html">set()</a>
。 - 监听器会收到错误并将其存储为最后一个错误 (
<a href="https://pub.dev/documentation/errflow/latest/errflow/ErrNotifier/lastError.html">lastError</a>
),以便稍后在函数内部检查。 - 监听器还会调用
<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>
提供的话。 - 如果满足条件,函数完成时会调用相关的错误处理程序。
错误处理程序将在文档后面详细解释。
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
更多关于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
还可以与状态管理结合使用,例如在 Provider
或 Riverpod
中:
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();
});
}
}