# 状态管理方案-Bloc
属于最难理解,同时也是必须要学会的内容。
# 工作原理
通过Events来触发bloc
通过Bloc修改state
通过states影响到视图层的变化
与redux,还有现在的主流的单向数据流一样。
# 安装依赖
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
bloc: ^7.2.0
flutter_bloc: ^7.3.0
然后使用flutter pub get
安装依赖。
# Bloc案例 - 计数器
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum CounterActions { increment, decrement }
class CounterBloc extends Bloc<CounterActions, int> {
CounterBloc() : super(0) {
// event -> 代表着UI层触发的事件
// emit -> 是一个可以修改state的函数
// state -> 集成state的getter方法
on<CounterActions>((event, emit) {
// TODO: implement event handler
if (event == CounterActions.increment) {
emit(state + 1);
}
if (event == CounterActions.decrement) {
emit(state - 1);
}
emit(state);
});
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterBloc(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, int>(
builder: (context, state) => Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$state',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 触发计数增加的事件
BlocProvider.of<CounterBloc>(context).add(CounterActions.increment);
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
);
}
}
# Bloc优化 - 待办清单
# 创建todo_bloc
可以借助vscode的插件快速创建bloc:
创建文件lib/bloc/todo/todo_bloc.dart
:
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
part 'todo_event.dart';
part 'todo_state.dart';
class TodoBloc extends Bloc<TodoEvent, TodoState> {
TodoBloc() : super(TodoInitial(const [])) {
// add
on<TodoAddEvent>((event, emit) {
// [...state, Item()]
emit(TodoInitial([
...state.lists,
Item(id: event.id, value: event.value),
]));
});
// remove
on<TodoRemoveEvent>((event, emit) {
emit(
TodoInitial(
state.lists..removeWhere((Item element) => element.id == event.id),
),
);
});
// clear
on<TodoClearEvent>((event, emit) {
emit(TodoInitial(const <Item>[]));
});
}
}
创建文件lib/bloc/todo/todo_event.dart
:
part of 'todo_bloc.dart';
abstract class TodoEvent {}
// add -> Item
class TodoAddEvent extends TodoEvent {
final String value;
static int _id = 0;
int get id => _id;
TodoAddEvent(this.value) {
_id++;
}
// final Item item;
// TodoAddEvent(this.item);
}
// remove -> id
class TodoRemoveEvent extends TodoEvent {
final int id;
TodoRemoveEvent(this.id);
}
// clear -> 不传参数
class TodoClearEvent extends TodoEvent {}
创建文件lib/bloc/todo/todo_state.dart
:
part of 'todo_bloc.dart';
class Item {
final int? id;
final String value;
Item({this.id, required this.value});
}
abstract class TodoState {
final List<Item> lists;
TodoState(this.lists);
}
// class -> event -> state
class TodoInitial extends TodoState {
TodoInitial(List<Item> lists) : super(lists);
}
# Bloc使用flutter_bloc
需要借助于flutter_bloc
:
- 组件
BlocProvider
用于创建bloc实例,MultiBlocProvider
多bloc合并注册; - 组件
BlocBuilder
用于使用父级最近一级的bloc实例; - 事件触发
BlocProvider.of<CounterBloc>(context)
调用add
方法;
import 'package:bloc_example/bloc/todo/todo_bloc.dart';
import 'package:bloc_example/child.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/counter/counter_bloc.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// 多个bloc,需要使用MultiBlocProvider类
return MultiBlocProvider(
providers: [
BlocProvider<CounterBloc>(
create: (context) => CounterBloc(),
),
BlocProvider<TodoBloc>(
create: (context) => TodoBloc(),
),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController controller = TextEditingController();
Widget build(BuildContext context) {
// 使用counter
return BlocBuilder<CounterBloc, int>(
builder: (context, state) => Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$state',
style: Theme.of(context).textTheme.headline4,
),
TextField(
controller: controller,
decoration: const InputDecoration(
hintText: '请输入待办清单项',
),
onSubmitted: (e) {
// 事件传递需要使用 BlocProvider.of
BlocProvider.of<TodoBloc>(context).add(TodoAddEvent(e));
controller.text = '';
},
),
// todo
BlocBuilder<TodoBloc, TodoState>(
builder: (context, state) {
return Expanded(
child: ListView(
children: state.lists
.map(
(e) => ListTile(
title: Text(e.value),
),
)
.toList(),
),
);
},
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ChildPage(),
),
);
},
child: const Text('跳转Child页面'),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 触发计数增加的事件
BlocProvider.of<CounterBloc>(context).add(CounterActions.increment);
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
);
}
}
# 子页面中使用Bloc
两种方式:
- 使用
BlocBuilder
- 使用
BlocProvider.of<CounterBloc>(context).state
来获取 state
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/counter/counter_bloc.dart';
class ChildPage extends StatelessWidget {
const ChildPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// BlocProvider add -> event
// 取state值的方式 -> scopedmodel.of
final int count = BlocProvider.of<CounterBloc>(context).state;
print(count);
return BlocBuilder<CounterBloc, int>(
builder: (context, state) => Scaffold(
appBar: AppBar(
title: Text('child页面'),
),
body: Center(
child: Text('这是child组件$state'),
),
),
);
}
}
# 理解bloc参数传递
Emitter的类型:
/// An event handler is responsible for reacting to an incoming [Event]
/// and can emit zero or more states via the [Emitter].
typedef EventHandler<Event, State> = FutureOr<void> Function(
Event event,
Emitter<State> emit,
);
由此,on
方法接收一个函数
阅读bloc的on
方法的源码:
void on<E extends Event>(
EventHandler<E, State> handler, {
EventTransformer<Event>? transformer,
}) {
assert(() {
final handlerExists = _handlerTypes.any((type) => type == E);
if (handlerExists) {
throw StateError(
'on<$E> was called multiple times. '
'There should only be a single event handler per event type.',
);
}
_handlerTypes.add(E);
return true;
}());
final _transformer = transformer ?? Bloc.transformer;
final subscription = _transformer(
_eventController.stream.where((event) => event is E),
(dynamic event) {
void onEmit(State state) {
if (isClosed) return;
if (this.state == state && _emitted) return;
onTransition(Transition(
currentState: this.state,
event: event as E,
nextState: state,
));
emit(state);
}
final emitter = _Emitter(onEmit);
final controller = StreamController<Event>.broadcast( // bloc基于`Stream`类进行的开发
sync: true,
onCancel: emitter.cancel,
);
void handleEvent() async {
void onDone() {
emitter.complete();
_emitters.remove(emitter);
if (!controller.isClosed) controller.close();
}
try {
_emitters.add(emitter);
await handler(event as E, emitter);
} catch (error, stackTrace) {
onError(error, stackTrace);
} finally {
onDone();
}
}
handleEvent();
return controller.stream;
},
).listen(null);
_subscriptions.add(subscription);
}
分析:
- 一个简单的发布订阅的模式;
onTransition
记录了bloc的不同的事件,对应的不同的state
阅读BlocBase
的源码:
/// {@template bloc_stream}
/// An interface for the core functionality implemented by
/// both [Bloc] and [Cubit].
/// {@endtemplate}
abstract class BlocBase<State> {
/// {@macro bloc_stream}
BlocBase(this._state) {
// ignore: invalid_use_of_protected_member
Bloc.observer.onCreate(this);
}
StreamController<State>? __stateController;
StreamController<State> get _stateController {
return __stateController ??= StreamController<State>.broadcast();
}
State _state;
bool _emitted = false;
/// The current [state].
State get state => _state;
/// The current state stream.
Stream<State> get stream => _stateController.stream;
/// Whether the bloc is closed.
///
/// A bloc is considered closed once [close] is called.
/// Subsequent state changes cannot occur within a closed bloc.
bool get isClosed => _stateController.isClosed;
/// Adds a subscription to the `Stream<State>`.
/// Returns a [StreamSubscription] which handles events from
/// the `Stream<State>` using the provided [onData], [onError] and [onDone]
/// handlers.
(
'Use stream.listen instead. Will be removed in v8.0.0',
)
StreamSubscription<State> listen(
void Function(State)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
return stream.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
/// Updates the [state] to the provided [state].
/// [emit] does nothing if the instance has been closed or if the
/// [state] being emitted is equal to the current [state].
///
/// To allow for the possibility of notifying listeners of the initial state,
/// emitting a state which is equal to the initial state is allowed as long
/// as it is the first thing emitted by the instance.
void emit(State state) {
if (_stateController.isClosed) return;
if (state == _state && _emitted) return;
onChange(Change<State>(currentState: this.state, nextState: state));
_state = state;
_stateController.add(_state);
_emitted = true;
}
/// Called whenever a [change] occurs with the given [change].
/// A [change] occurs when a new `state` is emitted.
/// [onChange] is called before the `state` of the `cubit` is updated.
/// [onChange] is a great spot to add logging/analytics for a specific `cubit`.
///
/// **Note: `super.onChange` should always be called first.**
/// ```dart
/// @override
/// void onChange(Change change) {
/// // Always call super.onChange with the current change
/// super.onChange(change);
///
/// // Custom onChange logic goes here
/// }
/// ```
///
/// See also:
///
/// * [BlocObserver] for observing [Cubit] behavior globally.
///
void onChange(Change<State> change) {
// ignore: invalid_use_of_protected_member
Bloc.observer.onChange(this, change);
}
/// Reports an [error] which triggers [onError] with an optional [StackTrace].
void addError(Object error, [StackTrace? stackTrace]) {
onError(error, stackTrace ?? StackTrace.current);
}
/// Called whenever an [error] occurs and notifies [BlocObserver.onError].
///
/// In debug mode, [onError] throws a [BlocUnhandledErrorException] for
/// improved visibility.
///
/// In release mode, [onError] does not throw and will instead only report
/// the error to [BlocObserver.onError].
///
/// **Note: `super.onError` should always be called last.**
/// ```dart
/// @override
/// void onError(Object error, StackTrace stackTrace) {
/// // Custom onError logic goes here
///
/// // Always call super.onError with the current error and stackTrace
/// super.onError(error, stackTrace);
/// }
/// ```
void onError(Object error, StackTrace stackTrace) {
// ignore: invalid_use_of_protected_member
Bloc.observer.onError(this, error, stackTrace);
assert(() {
throw BlocUnhandledErrorException(this, error, stackTrace);
}());
}
/// Closes the instance.
/// This method should be called when the instance is no longer needed.
/// Once [close] is called, the instance can no longer be used.
Future<void> close() async {
// ignore: invalid_use_of_protected_member
Bloc.observer.onClose(this);
await _stateController.close();
}
}
其中,包含了state
的get
方法,这也是为什么可以直接在on中使用state.属性
来进行取值(省略掉了this),同时Bloc是BlocBase的扩展类。