Flutter安全状态管理插件safe_bloc的使用
Flutter安全状态管理插件safe_bloc的使用
概述
safe_bloc
是对 bloc
状态管理库的扩展,用于处理代码中的意外异常,并将这些异常以可自定义的用户友好错误消息的形式展示给用户。该库通过 UnexpectErrorHandler
小部件来统一处理异常,并且可以根据需要自定义错误对话框和屏幕,以匹配应用程序的设计。此外,异常处理过程是可定制的,可以显示、记录或忽略异常。safe_bloc
还提供了 onUnexpectedError
回调函数,每次发生异常时都会被调用,特别适合用于异常日志记录。
该库区分了两种类型的错误:
- 错误状态:仅在初始屏幕加载时发生。如果屏幕加载失败,没有数据可以显示给用户,
UnexpectErrorHandler
会显示一个由errorScreen
参数表示的错误屏幕。 - 错误动作:通常发生在用户触发某个操作(例如点击按钮)时。此时我们不希望显示错误屏幕并清除已加载的数据,而是显示一个错误对话框,告知用户当前操作不可用,确保屏幕的现有数据仍然可用。
使用方法
1. 创建 UnexpectedError
状态
首先,创建一个在发生异常时发出的状态,该状态必须实现 UnexpectedErrorBase
接口。
sealed class MyAppState {}
final class MyAppErrorState extends MyAppState implements UnexpectedErrorBase {
[@override](/user/override)
final UnexpectedError error;
MyAppErrorState(this.error);
}
2. 使用 SafeBloc
或 SafeCubit
类
根据你使用的 Bloc
或 Cubit
,分别继承 SafeBloc
或 SafeCubit
类,并重写 errorState
getter。
Bloc
如果你使用的是 Bloc
,继承 SafeBloc
并重写 errorState
getter:
class MyAppBloc extends SafeBloc<MyAppEvent, MyAppState> {
// body
[@override](/user/override)
MyAppState Function(UnexpectedError error) get errorState => MyAppErrorState.new;
}
然后,在注册事件处理器时,使用 onSafe<EVENT>
代替标准的 on<EVENT>
:
onSafe<MyBlocEvent>(event, emit, {required trackingId}) async {
// do something
}
Cubit
如果你使用的是 Cubit
,继承 SafeCubit
并重写 errorState
getter。然后将所有公共的 Cubit
方法包装在 safeCall
中:
class MyAppCubit extends SafeCubit<MyAppState> {
MyAppCubit(super.initialState);
FutureOr<void> someMethod() => safeCall((trackingId) {
// do something
});
[@override](/user/override)
MyAppState Function(UnexpectedError error) get errorState => MyAppErrorState.new;
}
对于同步方法,可以使用 safeCallSync
:
void someSyncMethod() => safeCallSync((trackingId) {
// do something
});
每次发生异常时,父类会捕获异常并发出 MyAppErrorState
,该状态包含一个 UnexpectedError
对象,其中包含有关异常的详细信息。
onSafe
和 safeCall
提供了一个唯一的 trackingId
,用于跟踪用户操作。它们还提供了一些额外的参数:
devErrorMessage
(可选):传递给UnexpectedError
对象的字符串消息,可用于日志记录。isAction
:布尔值,指示该方法是错误动作还是错误状态。如果设置为true
,UnexpectedErrorHandler
会显示错误对话框或调用onErrorAction
回调。默认为false
,即显示由errorScreen
参数指定的错误屏幕。ignoreError
:布尔值,指示是否忽略异常。如果设置为true
,异常会被捕获,但不会发出MyAppErrorState
。onIgnoreError
(可选):如果发生异常且ignoreError
设置为true
,则调用此回调。errorMapper
(可选):将个别异常映射到Bloc
或Cubit
状态的函数。如果为null
,所有异常都将映射到MyAppErrorState
。
此外,SafeBloc
和 SafeCubit
还允许重写 onUnexpectedError
方法,该方法在每次抛出异常时被调用,可用于异常日志记录。
3. 在 UI 中展示错误
使用 UnexpectedErrorHandler
小部件来显示错误:
UnexpectedErrorHandler<MyAppBloc, MyAppState>(
errorScreen: (context, error) => Text(error.toString()),
onErrorAction: (context, error) {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(title: Text(error.toString()));
},
);
},
child: MyWidget(),
)
UnexpectedErrorHandler
提供了 errorScreen
参数用于在初始屏幕加载时显示错误,以及 onErrorAction
参数用于处理错误动作。如果 onSafe
或 safeCall
的 isAction
参数设置为 true
,则会调用 onErrorAction
回调。
4. 使用呈现事件
safe_bloc
使用 bloc_presentation
库来处理用户错误动作事件。bloc_presentation
为 Bloc
或 Cubit
添加了一个流,用于向用户呈现一次性事件(例如对话框或 Snackbar)。safe_bloc
使用 BaseEffect
作为呈现事件,并继承 UnexpectedErrorEffect
类用于异常处理。如果你有特定的呈现事件,可以在 SafeBlocWithPresentation
或 SafeCubitWithPresentation
中实现自己的呈现事件。
class MyAppCubit extends SafeCubitWithPresentation<MyAppState, MyAppPresentationEvent> {
MyAppCubit(super.initialState);
FutureOr<void> someMethod() => safeCall((trackingId) {
// do something
});
[@override](/user/override)
MyAppState Function(UnexpectedError error) get errorState => MyAppErrorState.new;
[@override](/user/override)
MyAppPresentationEvent Function(UnexpectedError error) get errorEffect => MyAppErrorPresentationEvent.new;
}
完整示例 Demo
以下是一个完整的示例,展示了如何在计数器应用中使用 safe_bloc
:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:safe_bloc/safe_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MainApp());
}
/// Flutter Counter app that stores the count to shared preferences and loads it back at the app start.
class MainApp extends StatelessWidget {
const MainApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: BlocProvider<CounterBloc>(
create: (_) => CounterBloc()..add(LoadCounter()),
child: const CounterView(),
),
),
);
}
}
class CounterView extends StatelessWidget {
const CounterView({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: UnexpectedErrorHandler<CounterBloc, CounterState>(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
if (state is! CounterLoaded) return const CircularProgressIndicator();
return Text(state.count.toString(), style: textTheme.displayMedium);
},
),
),
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => context.read<CounterBloc>().add(IncrementPressed()),
),
],
),
);
}
}
sealed class CounterEvent {}
final class IncrementPressed extends CounterEvent {}
final class LoadCounter extends CounterEvent {}
sealed class CounterState {}
final class CounterLoading extends CounterState {}
final class CounterLoaded extends CounterState {
final int count;
CounterLoaded({required this.count});
}
final class CounterBlocError extends CounterState implements UnexpectedErrorBase {
[@override](/user/override)
final UnexpectedError error;
CounterBlocError(this.error);
}
class CounterBloc extends SafeBloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterLoading()) {
// `isAction` parameter is set to false for initial data loading
onSafe<LoadCounter>(_loadCounter, isAction: false);
// `isAction` parameter is set to true for user action
onSafe<IncrementPressed>(_increment, isAction: true);
}
Future<void> _increment(
IncrementPressed event,
SafeEmitter<CounterState> emit, {
required String trackingId,
}) async {
if (state case final CounterLoaded loaded) {
final incrementedCount = loaded.count + 1;
final preferences = await SharedPreferences.getInstance();
await preferences.setInt('count_key', incrementedCount);
// Uncomment the exception throwing to test the error dialog
// throw Exception("Something wrong happened");
emit(CounterLoaded(count: incrementedCount));
}
}
Future<void> _loadCounter(LoadCounter event, SafeEmitter<CounterState> emit, {required String trackingId}) async {
final preferences = await SharedPreferences.getInstance();
final count = preferences.getInt('count_key');
// Uncomment the exception throwing to test the error screen
// throw Exception("Something wrong happened when counter loading");
emit(CounterLoaded(count: count ?? 0));
}
[@override](/user/override)
CounterState Function(UnexpectedError error) get errorState => CounterBlocError.new;
[@override](/user/override)
Future<void> onUnexpectedError(Object? error, StackTrace stackTrace, String trackingId) async {
if (kDebugMode) {
print('Exception: $error, $stackTrace');
}
}
}
更多关于Flutter安全状态管理插件safe_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter安全状态管理插件safe_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用safe_bloc
进行安全状态管理的代码示例。safe_bloc
是一个增强版的Bloc库,旨在提供更安全、更简洁的状态管理解决方案。
首先,确保你已经在pubspec.yaml
文件中添加了safe_bloc
依赖:
dependencies:
flutter:
sdk: flutter
safe_bloc: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
接下来,让我们创建一个简单的示例,展示如何使用safe_bloc
来管理计数器应用的状态。
1. 创建CounterEvent类
首先,定义事件类,这些事件将触发Bloc中的状态更改。
// counter_event.dart
import 'package:equatable/equatable.dart';
abstract class CounterEvent extends Equatable {
const CounterEvent();
@override
List<Object> get props => [];
}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
2. 创建CounterState类
接下来,定义状态类,这些状态将表示Bloc的当前状态。
// counter_state.dart
import 'package:equatable/equatable.dart';
class CounterState extends Equatable {
final int count;
const CounterState({required this.count});
@override
List<Object> get props => [count];
}
3. 创建CounterBloc类
然后,定义Bloc类,它将处理事件并发出新的状态。
// counter_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:safe_bloc/safe_bloc.dart';
import 'counter_event.dart';
import 'counter_state.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> with SafeBloc {
CounterBloc() : super(CounterState(count: 0)) {
on<IncrementEvent>((event, emit) {
emit(state.copyWith(count: state.count + 1));
});
on<DecrementEvent>((event, emit) {
emit(state.copyWith(count: state.count - 1));
});
}
}
4. 使用CounterBloc在UI中
最后,在Flutter的UI组件中使用CounterBloc
。
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
import 'counter_event.dart';
void main() {
runApp(BlocProvider(
create: (_) => CounterBloc(),
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Safe Bloc Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'${state.count}',
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(DecrementEvent()),
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
],
),
),
);
}
}
在这个示例中,我们创建了一个简单的计数器应用,使用safe_bloc
进行状态管理。我们定义了事件和状态类,创建了一个处理这些事件的Bloc类,并在UI组件中使用了这个Bloc来显示和更新计数器的值。
这个示例展示了safe_bloc
的基本用法,你可以根据需要扩展和修改这个示例以适应更复杂的应用场景。