Flutter状态管理框架Bloc学习

610 阅读3分钟

bloc

Cubit

image.png 继承自BlockBase,可以扩展到管理任何类型的状态,一个Cubit可以公开触发状态变化的函数;

状态是从 Cubit 中输出的,代表应用程序状态的一部分。可以通知 UI 组件状态,并根据当前状态重绘其自身的某些部分。

  1. 创建Cubit cubit = CounterCubit(0)
  2. 状态变化 emit()
  3. 使用Cubit cubit.increment cubit.state cubit.close
  4. 流的用例 cubit.stream.listen
  5. 观察Cubit onChange()
  6. 错误处理 onError()
class CounterCubit extends Cubit<int> {
  CounterCubit(int initialState) : super(initialState);

  void increment() {
    addError(Exception('increment error!'),StackTrace.current);
    emit(state + 1);
  }

  @override
  void onChange(Change<int> change) {
    super.onChange(change);
    print(change);
  }

  @override
  void onError(Object error, StackTrace stackTrace) {
    print('$error, $stackTrace');
    super.onError(error, stackTrace);
  }
}

Bloc

image.png

Bloc 是一个更高级的类,它依赖事件来触发状态的改变而不是函数。 Bloc 也扩展了 BlocBase ,这意味着它有着类似于 Cubit 的API。然而, Blocs 不是在 Bloc 上调用函数然后直接发出一个新的状态,而是接收事件并且将传入的事件转换为状态传出。

  1. 创建Bloc:创建过程类似于Cubit,但是还需要定义事件
  2. 状态改变:on<Event>
  3. Stream用例:和Cubit一样
  4. 观察一个Bloc:onChangeonTransition
  5. 支持BlocObserver
  6. 错误处理: onError
abstract class CounterEvent{}

class CounterIncrementPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc(super.initialState) {
    on<CounterIncrementPressed>((event, emit) {
      emit(state + 1);
    });
  }

  @override
  void onTransition(Transition<CounterEvent, int> transition) {
    super.onTransition(transition);
    print(transition);
  }
}

Bloc 永远不要直接发出新状态。相反,必须响应 EventHandler 中的传入事件,输出每个状态更改。

Bloc 和 Cubits 都会忽略重复的状态。如果我们产生或发出状态 State nextState 当 State == nextState 时,则不会发生状态变化。

BlocObserver

BlocObserver可以观察到所有的变化

class SimpleBlocObserver extends BlocObserver {
  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    print('${bloc.runtimeType} $change');
  }

  @override
  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
    print('${bloc.runtimeType} $error $stackTrace');
    super.onError(bloc, error, stackTrace);
  }
}

Cubit vs. Bloc

Cubit的优势

简单:当创建一个Cubit时,我们只需要定义状态以及我们想要公开的改变状态的函数即可, 相比之下,创建Bloc时,我们必须定义状态、事件和EventHandler实现;

Bloc的优势

可追溯性:可以知道状态变化的顺序以及触发这些变化的确切原因。对于应用程序功能至关重要的状态,使用更多事件驱动的方法来捕获状态变化之外的所有事件可能会非常有益。

一个常见的用例可能是管理 AuthenticationState。为了简单起见,假设我们可以通过 enum 来表示 AuthenticationState

enum AuthenticationState { unknown, authenticated, unauthenticated }

当发生主动注销(点击了注销按钮要求退出程序),或者是被动注销(访问令牌已被撤消,并被强制注销)时,使用Bloc可以清楚地跟踪应用程序状态如何达到特定状态⬇️:

Transition {
  currentState: AuthenticationState.authenticated,
  event: LogoutRequested,
  nextState: AuthenticationState.unauthenticated
}

高级的事件转换:Bloc优于Cubit的另一个领域是我们需要利用反应性运算符,例如:buffer、debounceTime、throttle等 使用Bloc,可以重写EventTransformer可以直接设置事件的防抖或者节流 transformer: debounce(const Duration(milliseconds: 300)),

flutter_bloc

Bloc Widgets

BlocBuilder

BlocBuilder和StreamBuilder相似,接收Blocbuilder两个方法,在接收到新的状态(State)时处理构建部件; 和StreamBuilder一样,builder方法会被潜在的触发很多次并且应该是一个返回一个Widget

使用buildWhen确保builder触发时机;

关于bloc参数:

  • 如果省略,BlocBuilder将使用BlocProvider和当前的BuildContext自动执行查找
  • 当希望提供一个范围仅限于单个Widget且无法通过BlocProvider和当前BuildContext访问的块时,才指定Bloc
class BlocBuilderCounterTest extends StatelessWidget {
  const BlocBuilderCounterTest({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
        create: (_) => CounterCubit(1),
        child: const Center(
          child: BlocBuilderCounterTestSub(),
        ));
  }
}

class BlocBuilderCounterTestSub extends StatefulWidget {
  const BlocBuilderCounterTestSub({Key? key}) : super(key: key);

  @override
  State<BlocBuilderCounterTestSub> createState() => _BlocBuilderCounterTestSubState();
}

class _BlocBuilderCounterTestSubState extends State<BlocBuilderCounterTestSub> {
  // CounterCubit cubitSmallScope = CounterCubit(20);

  @override
  Widget build(BuildContext context) {
    return BlocListener<CounterCubit, int>(
      listener: (context, state) {
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('这是一个SnackBar${state.toString()}!')));
      },
      child: BlocBuilder<CounterCubit, int>(
          // bloc: cubitSmallScope, //指定bloc,使用当前的bloc
          //未指定bloc,去BlocProvider中查找
          builder: (context, state) {
        return Center(
          child: Column(
            children: [
              Text(
                state.toString(),
                style: const TextStyle(inherit: true, fontSize: 20),
              ),
              // const Spacer(flex: 2,),
              TextButton(
                  onPressed: () {
                    context.read<CounterCubit>().increment();
                  },
                  child: const Icon(
                    Icons.add,
                    size: 30,
                  ))
            ],
          ),
        );
      }),
    );
  }
}

BlocSelector

BlocSelector和BlocBuilder类似,但它可以允许选择基于当前bloc状态的新值来过滤更新。如果所选值不更改,则会阻止不必要的构建。关于bloc参数和BlocBuilder一样

class BlocSelectorCounterTest extends StatelessWidget {
  const BlocSelectorCounterTest({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
        create: (_) => CounterCubit(1),
        child: const Center(
          child: BlocBuilderCounterTestSub(),
        ));
  }
}

class BlocSelectorCounterTestSub extends StatefulWidget {
  const BlocSelectorCounterTestSub({Key? key}) : super(key: key);

  @override
  State<BlocSelectorCounterTestSub> createState() => _BlocSelectorCounterTestSubState();
}

class _BlocSelectorCounterTestSubState extends State<BlocSelectorCounterTestSub> {
  CounterCubit cubitSmallScope = CounterCubit(20);

  @override
  Widget build(BuildContext context) {
    return BlocSelector<CounterCubit, int, int>(selector: (state) {
      return state;
    },
        // bloc: cubitSmallScope, //指定bloc,使用当前的bloc
        //未指定bloc,去BlocProvider中查找
        builder: (context, state) {
      return Center(
        child: Column(
          children: [
            Text(
              state.toString(),
              style: const TextStyle(inherit: true, fontSize: 20),
            ),
            // const Spacer(flex: 2,),
            TextButton(
                onPressed: () {
                  cubitSmallScope.increment();
                },
                child: const Icon(
                  Icons.add,
                  size: 30,
                ))
          ],
        ),
      );
    });
  }
}

BlocProvider

BlocProvider类似于InheritedWidget,创建blocs,并且向子树提供(注意是是以懒加载方式创建的,可以通过lazy参数取消懒加载);

子树中查找bloc的方式:

  • context.read<BlocA>()
  • BlocProvider.of<BlocA>(context)

MultiBlocProvider提供多个bloc,而避免嵌套,提升可读性;

BlocListener

BlocListener是一个Widget,接受一个BlocWidgetListener和一个可选的Bloc。并调用listener以响应该状态的变化;与BlocBuilder中的builder不同,每个状态(State)更改(不包括initialState在内的)仅被调用一次listener,关于bloc参数和BlocBuilder一样,也提供了时机限制函数,listenWhen,一般用于toast/dialog

Widget build(BuildContext context) {
  return BlocListener<CounterCubit, int>(
    listener: (context, state) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('这是一个SnackBar${state.toString()}!')));
    },
    child: BlocBuilder<CounterCubit, int>(
        // bloc: cubitSmallScope, //指定bloc,使用当前的bloc
        //未指定bloc,去BlocProvider中查找
        builder: (context, state) {
      return Center(
        child: Column(
          children: [
            Text(
              state.toString(),
              style: const TextStyle(inherit: true, fontSize: 20),
            ),
            // const Spacer(flex: 2,),
            TextButton(
                onPressed: () {
                  context.read<CounterCubit>().increment();
                },
                child: const Icon(
                  Icons.add,
                  size: 30,
                ))
          ],
        ),
      );
    }),
  );
}

BlocConsumer

BlocConsumer是BlocListener+BlocBuilder结合

RepositoryProvider

RepositoryProvider 借用Provider 实现了一个组件树上的局部共享对象容器。通过这个容器,为RepositoryProvider的子组件树注入了共享对象,使得子组件可以从 context 中或使用RepositoryProvider.of 静态方法获取共享对象。通过这种方式避免了组件树的层层传值,使得代码更为简洁和易于维护。 www.jianshu.com/p/cd93d7c81…