# 状态管理方案-Bloc

属于最难理解,同时也是必须要学会的内容。

# 工作原理

bloc_architecture_full

  • 通过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:

image-20210925175925764

创建文件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参数传递

image-20210925182535076

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();
  }
}

其中,包含了stateget方法,这也是为什么可以直接在on中使用state.属性来进行取值(省略掉了this),同时Bloc是BlocBase的扩展类。