乐趣区

关于android-studio:flutterbloc使用解析骚年你还在手搭bloc吗

前言

  • 首先,有很多的文章在说 flutter bloc 模式的利用,然而百分之八九十的文章都是在说,应用 StreamController+StreamBuilder 搭建 bloc,晋升性能的会加上 InheritedWidget,这些文章看了很多,真正写应用 bloc 作者开发的 flutter_bloc 却少之又少。没方法,只能去 bloc 的 github 下来找应用形式,最初去 bloc 官网翻文档。
  • 各位靓仔,就不能好好说说 flutter_bloc 的应用吗?非要各种搬运 bloc 模式提出作者的那俩篇文章。当初,搞的杂家要本人去翻文档总结(手动滑稽)。

我的项目成果(倡议 PC 浏览器关上)

  • Bloc 范例成果
  • Cubit 范例成果

上面是 Flutter_Bloc 历程的一系列链接

  • Flutter_Bloc 起源
  • Flutter_Bloc 模式优化
  • Flutter_Bloc 诞生
  • Flutter_Bloc 官网文档

    后面三个,是 bloc 作者写的 bloc 模式文档,典型的观察者模式的利用,最原始的就是 java 中 CallBack 模式。前俩篇文章就是咱们这些搬运工的次要“参考”的材料起源,这三篇文章在掘金上有翻译版,搜下 bloc 就能找到。最初一篇文章就是我次要总结演绎的源泉,作者在官网上写了好几个 demo:计时器,登录,Todos,天气等等,大家能够本人去看看。

问题

首次应用 flutter_bloc 框架,可能会有几个疑难

  • state 外面定义了太多变量,某个事件只须要更新其中一个变量,其它的变量赋雷同值麻烦
  • 进入某个模块,进行初始化操作:简单的逻辑运算,网络申请等,入口在哪定义

成果

  • 好了,哔哔了一堆,看下咱们要用 flutter_bloc 实现的成果。

  • 间接开 Chrome 演示,大家在虚拟机上跑也一样。

援用

  • 先阐明下,bloc 给的 api 很多,不同的 api 针对与解决场景不同,我要是把官网那些 api 全抄过也没啥意义;不,也有可能能够装币,我要是不阐明,大家说不定认为是我本人总结的呢!哈哈。
  • OK,大家要是想晓得全场景的应用,能够去官网翻翻文档,我感觉学习一个模式或者框架的时候,最次要的是把主流程跑通,起码能够符合标准的堆页面,这样的话,就能够把这玩意用起来,再遇到想要的什么细节,就能够本人去翻文档,毕竟大体上曾经懂了,写过了几个页面,也有些领会,再去翻文档就很快能了解了。

flutter_bloc: ^6.0.6 #状态治理框架
equatable: ^1.2.3 #加强组件相等性判断 
  • 看看 flutter_bloc 都推到 6.0 了,别再用 StreamController 手搭 Bloc 了!

插件

在 Android Studio 设置的 Plugins 里,搜寻:Bloc

装置重启下,就 OK 了

  • 右击相应的文件夹,抉择“Bloc Class”,我在 main 文件夹新建的,填入的名字:main,就主动生成上面三个文件;:main_bloc,main_event,main_state;main_view 是我本人新建,用来写页面的。

  • 是不是感觉,还在手动新建这些 bloc 文件 low 爆了;就如同 fish_redux,不必插件,让我手动去创立那六个文件,写那些模板代码,真的要原地爆炸。

Bloc 范例

初始化代码

来看下这三个生成的 bloc 文件:main_bloc,main_event,main_state

  • main_bloc:这里就是咱们次要写逻辑的页面了

    • mapEventToState 办法只有一个参数,前面主动带了一个逗号,格式化代码就分三行了,倡议删掉逗号,格式化代码。
class MainBloc extends Bloc<MainEvent, MainState> {MainBloc() : super(MainInitial());

  @override
  Stream<MainState> mapEventToState(MainEvent event,) async* {// TODO: implement mapEventToState}
}
  • main_event:这里是执行的各类事件,有点相似 fish_redux 的 action 层
@immutable
abstract class MainEvent {}
  • main_state:状态数据放在这里保留,直达
@immutable
abstract class MainState {}

class MainInitial extends MainState {}

实现

  • 阐明

    • 这里对于简略的页面,state 的应用形象状态继承实现的形式,未免有点麻烦,这里我进行一点小改变,state 的实现类别有很多,官网写 demo 也有不必抽象类,间接 class,相似实体类的形式开搞的。
    • 老夫在代码关键点写上 ”///” 类型正文,大家认真看看,拷进 Android Studio 外面,这些中央会变绿!大家好好领会下绿色代码!
  • main_bloc

    • state 变量是框架外部定义的,会默认保留上一次同步的 MainSate 对象的值
class MainBloc extends Bloc<MainEvent, MainState> {MainBloc() : super(MainState(selectedIndex: 0, isExtended: false));

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view 中增加的事件,会在此处回调,此处解决完数据,将数据 yield,BlocBuilder 就会刷新组件
    if (event is SwitchTabEvent) {
      /// 获取到 event 事件传递过去的值, 咱们拿到这值塞进 MainState 中
      /// 间接在 state 上扭转外部的值, 而后 yield, 只能触发一次 BlocBuilder, 它外部会比拟上次 MainState 对象, 如果雷同, 就不 build
      yield MainState()
        ..selectedIndex = event.selectedIndex
        ..isExtended = state.isExtended;
    } else if (event is IsExtendEvent) {yield MainState()
        ..selectedIndex = state.selectedIndex
        ..isExtended = !state.isExtended;
    }
  }
}
  • main_event:在这里就能看见,view 触发了那些事件了;保护起来也很爽,看看这里,也很快能懂页面在干嘛了
@immutable
abstract class MainEvent extends Equatable{const MainEvent();
}
/// 切换 NavigationRail 的 tab
class SwitchTabEvent extends MainEvent{
  final int selectedIndex;

  const SwitchTabEvent({@required this.selectedIndex});

  @override
  List<Object> get props => [selectedIndex];
}
/// 开展 NavigationRail, 这个逻辑比较简单, 就不必传参数了
class IsExtendEvent extends MainEvent{const IsExtendEvent();

  @override
  List<Object> get props => [];}
  • main_state:state 有很多种写法,在 bloc 官网文档上,不同我的项目 state 的写法也很多

    • 这边变量名能够设置为私用,用 get 和 set 可选择性的设置读写权限,因为我这边设置的俩个变量全是必用的,读写均要,就设置私有类型,不必下划线“_”去标记公有了。
    • 对于生成的模板代码,咱们在这:去掉 @immutable 注解,去掉 abstract;
    • 这里说下加上 @immutable 和 abstract 的作用,这边是为了标定不同状态,这种写法,会使得代码变得更加麻烦,用 state 不同状态去标定业务事件,代价太大,这边用一个变量去标定,很容易轻松代替
class MainState{
   int selectedIndex;
   bool isExtended;
  
   MainState({this.selectedIndex, this.isExtended});
}
  • main_view

    • 这边就是咱们的界面层了,很简略,将须要刷新的组件,用 BlocBuilder 包裹起来,应用 BlocBuilder:提供的 state 去赋值就 ok 了,context 去增加执行的事件,context 用 StatelessWidget 中提供的或者 BlocBuilder 提供的都行
void main() {runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: MainPage(),
    );
  }
}

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /// 创立 BlocProvider 的,表明该 Page,咱们是用 MainBloc,MainBloc 是属于该页面的 Bloc 了
    return BlocProvider(create: (BuildContext context) => MainBloc(),
      child: BodyPage(),);
  }
}

class BodyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: Text('Bloc')),
      body: totalPage(),);
  }
}

Widget totalPage() {
  return Row(
    children: [navigationRailSide(),
      Expanded(child: Center(child: BlocBuilder<MainBloc, MainState>(builder: (context, state) {
          /// 看这看这:刷新组件!return Text("selectedIndex:" + state.selectedIndex.toString());
        }),
      ))
    ],
  );
}

// 减少 NavigationRail 组件为侧边栏
Widget navigationRailSide() {
  // 顶部 widget
  Widget topWidget = Center(
    child: Padding(padding: const EdgeInsets.all(8.0),
      child: Container(
          width: 80,
          height: 80,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            image: DecorationImage(image: NetworkImage("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3383029432,2292503864&fm=26&gp=0.jpg"),
                fit: BoxFit.fill),
          )),
    ),
  );

  // 底部 widget
  Widget bottomWidget = Container(
    child: BlocBuilder<MainBloc, MainState>(builder: (context, state) {
        return FloatingActionButton(onPressed: () {
            /// 增加 NavigationRail 开展, 膨胀事件
            context.bloc<MainBloc>().add(IsExtendEvent());
          },
          /// 看这看这:刷新组件!child: Icon(state.isExtended ? Icons.send : Icons.navigation),
        );
      },
    ),
  );

  return BlocBuilder<MainBloc, MainState>(builder: (context, state) {
    return NavigationRail(
      backgroundColor: Colors.white12,
      elevation: 3,
      /// 看这看这:刷新组件!extended: state.isExtended,
      labelType: state.isExtended ? NavigationRailLabelType.none : NavigationRailLabelType.selected,
      // 侧边栏中的 item
      destinations: [
        NavigationRailDestination(icon: Icon(Icons.add_to_queue),
            selectedIcon: Icon(Icons.add_to_photos),
            label: Text("测试一")),
        NavigationRailDestination(icon: Icon(Icons.add_circle_outline),
            selectedIcon: Icon(Icons.add_circle),
            label: Text("测试二")),
        NavigationRailDestination(icon: Icon(Icons.bubble_chart),
            selectedIcon: Icon(Icons.broken_image),
            label: Text("测试三")),
      ],
      // 顶部 widget
      leading: topWidget,
      // 底部 widget
      trailing: bottomWidget,
      selectedIndex: state.selectedIndex,
      onDestinationSelected: (int index) {
        /// 增加切换 tab 事件
        context.bloc<MainBloc>().add(SwitchTabEvent(selectedIndex: index));
      },
    );
  });
}

Bloc 范例优化

反思

从下面的代码来看,理论存在几个隐式问题,这些问题,刚开始应用时候,没异样的感觉,然而应用 bloc 久了后,感觉必定越来越强烈

  • state 问题

    • 初始化问题:这边初始化是在 bloc 里,间接在构造方法外面赋初值的,state 中一旦变量多了,还是这么写,会感觉极其好受,不好治理。须要优化
    • 能够看见这边咱们只改变 selectedIndex 或者 isExtended;另一个变量不须要变动,须要放弃上一次的数据,进行了此类:state.selectedIndex 或者 state.isExtended 赋值,一旦变量达到十几个乃至几十个,还是如此写,是让人极其解体的。须要优化
  • bloc 问题

    • 如果进行一个页面,须要进行简单的运算或者申请接口后,能力通晓数据,进行赋值,这里必定须要一个初始化入口,初始化入口须要怎么去定义呢?

优化实现

这边残缺走一下流程,让大家能有个残缺的思路

  • state:首先来看看咱们对 state 中的优化,这边进行了俩个很重要优化,减少俩个办法:init() 和 clone()

    • init():这里初始化对立用 init() 办法去治理
    • clone():这边克隆办法,是十分重要的,一旦变量达到俩位数以上,就能粗浅领会该办法是如许的重要
class MainState {
  int selectedIndex;
  bool isExtended;

  /// 初始化办法, 根底变量也须要赋初值, 不然会报空异样
  MainState init() {return MainState()
      ..selectedIndex = 0
      ..isExtended = false;
  }

  ///clone 办法, 此办法实现参考 fish_redux 的 clone 办法
  /// 也是对官网 Flutter Login Tutorial 这个 demo 中 copyWith 办法的一个优化
  ///Flutter Login Tutorial(https://bloclibrary.dev/#/flutterlogintutorial)MainState clone() {return MainState()
      ..selectedIndex = selectedIndex
      ..isExtended = isExtended;
  }
}
  • event

    • 这边定义一个 MainInit() 初始化办法,同时去掉 Equatable 继承,在我目前的应用中,感觉它用途不大。。。
@immutable
abstract class MainEvent {}

/// 初始化事件, 这边目前不须要传什么值
class MainInitEvent extends MainEvent {}

/// 切换 NavigationRail 的 tab
class SwitchTabEvent extends MainEvent {
  final int selectedIndex;

  SwitchTabEvent({@required this.selectedIndex});
}

/// 开展 NavigationRail, 这个逻辑比较简单, 就不必传参数了
class IsExtendEvent extends MainEvent {}
  • bloc

    • 这减少了初始化办法,请留神,如果须要进行异步申请,同时须要将相干逻辑提炼一个办法,咱们在这里配套 Future 和 await 就能解决在异步场景下同步数据问题
    • 这里应用了克隆办法,能够发现,咱们只有关注本人须要扭转的变量就行了,其它的变量都在外部赋值好了,咱们不须要去关注;这就大大的便捷了页面中有很多变量,只须要变动一俩个变量的场景
    • 留神:如果变量的数据未扭转,界面相干的 widget 是不会重绘的;只会重绘变量被扭转的 widget
class MainBloc extends Bloc<MainEvent, MainState> {MainBloc() : super(MainState().init());

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view 中增加的事件,会在此处回调,此处解决完数据,将数据 yield,BlocBuilder 就会刷新组件
    if (event is MainInitEvent) {yield await init();
    } else if (event is SwitchTabEvent) {
      /// 获取到 event 事件传递过去的值, 咱们拿到这值塞进 MainState 中
      /// 间接在 state 上扭转外部的值, 而后 yield, 只能触发一次 BlocBuilder, 它外部会比拟上次 MainState 对象, 如果雷同, 就不 build
      yield switchTap(event);
    } else if (event is IsExtendEvent) {yield isExtend();
    }
  }

  /// 初始化操作, 在网络申请的状况下, 须要应用如此办法同步数据
  Future<MainState> init() async {return state.clone();
  }

  /// 切换 tab
  MainState switchTap(SwitchTabEvent event) {return state.clone()..selectedIndex = event.selectedIndex;
  }

  /// 是否开展
  MainState isExtend() {return state.clone()..isExtended = !state.isExtended;
  }
}
  • view

    • view 层代码太多,这边只减少了个初始化事件,就不从新把全副代码贴出来了,初始化操作间接在创立的时候,在 XxxBloc 上应用 add() 办法就行了,就能起到进入页面,初始化一次的成果;add() 办法也是 Bloc 类中提供的,遍历事件的时候,就顺便查看了 add() 这个办法是否增加了事件;阐明,这是框架顺便提供了一个初始化的办法
    • 这个初始化形式是在官网示例找到的

      • 我的项目名:Flutter Infinite List Tutorial
      • 我的项目地址:flutter-infinite-list-tutorial
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      /// 在 MainBloc 上应用 add 办法, 增加初始化事件
      create: (BuildContext context) => MainBloc()..add(MainInitEvent()),
      child: BodyPage(),);
  }
}
/// 下方其余代码省略...........

搞定

  • OK,通过这样的优化,解决了几个痛点。理论在 view 中重复是要用 BlocBuilder 去更新 view,写起来有点麻烦,这里咱们能够写一个,将其中 state 和 context 变量,往提出来的 Widget 办法传值,也是蛮不错的
  • 大家放弃观察者模式的思维就行了;观察者(回调刷新控件)和被观察者(产生相应事件,增加事件,去告诉观察者),bloc 层是处于观察者和被观察者两头的一层,咱们能够在 bloc 外面搞业务,搞逻辑,搞网络申请,不能搞基;拿到 Event 事件传递过去的数据,把解决好的、符合要求的数据返回给 view 层的观察者就行了。
  • 应用框架,不拘泥框架,在观察者模式的思维上,灵便的去应用 flutter_bloc 提供 Api,这样能够大大的缩短咱们的开发工夫!

Cubit 范例

  • Cubit 是 Bloc 模式的一种简化版,去掉了 event 这一层,对于简略的页面,用 Cubit 来实现,开发体验是大大的好啊,上面介绍下该种模式的写法

创立

  • 首先创立 Cubit 一组文件,抉择“Cubit Class”,点击,新建名称填写:Counter

新建好后,他会生成俩个文件:counter_cubit,counter_state,来看下生成的代码

原始生成代码

  • counter_cubit
class CounterCubit extends Cubit<CounterState> {CounterCubit() : super(CounterInitial());
}
  • counter_state
@immutable
abstract class CounterState {}

class CounterInitial extends CounterState {}

依照生成的这种 state 形式去写,比拟麻烦,这边调整下

调整后代码

  • counter_cubit
class CounterCubit extends Cubit<CounterState> {CounterCubit() : super(CounterState().init());
}
  • counter_state
class CounterState {
  /// 初始化办法
  CounterState init() {return CounterState();
  }

  /// 克隆办法, 针对于刷新界面数据
  CounterState clone() {return CounterState();
  }
}

OK,这样调整了下,上面写起来就会难受很多,也会很省事

实现计时器

  • 来实现下一个灰常简略的计数器

成果

  • 来看下实现成果吧,这边不上图了,大家点击上面的链接,能够间接体验 Cubit 模式写的计时器
  • 实现成果:点我体验实际效果

实现

实现很简略,三个文件就搞定,看下流程:state -> cubit -> view

  • state:这个很简略,加个计时变量
class CounterState {
  int count;

  CounterState init() {return CounterState()..count = 0;
  }

  CounterState clone() {return CounterState()..count = count;
  }
}
  • cubit

    • 这边加了个自增办法:increase()
    • event 层理论是所有行为的一种整合,不便对逻辑过于简单的页面,所有行为的一种保护;然而过于简略的页面,就那么几个事件,还独自保护,就没什么必要了
    • 在 cubit 层写的公共办法,在 view 外面能间接调用,更新数据应用:emit()
    • cubit 层应该能够算是:bloc 层和 event 层一种联合后的简写
class CounterCubit extends Cubit<CounterState> {CounterCubit() : super(CounterState().init());

  /// 自增
  void increase() => emit(state.clone()..count = ++state.count);
}
  • view

    • view 层的代码就非常简单了,点击办法外面调用 cubit 层的自增办法就 ok 了
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(create: (BuildContext context) => CounterCubit(),
      child: BlocBuilder<CounterCubit, CounterState>(builder: _counter),
    );
  }

  Widget _counter(BuildContext context, CounterState state) {
    return Scaffold(appBar: AppBar(title: const Text('Cubit 范例')),
      body: Center(child: Text('点击了 ${state.count} 次', style: TextStyle(fontSize: 30.0)),
      ),
      floatingActionButton: FloatingActionButton(onPressed: () => context.bloc<CounterCubit>().increase(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

总结

在 Bloc 模式外面,如果页面不是过于简单,应用 Cubit 去写,根本齐全够用了;然而如果业务过于简单,还是须要用 Bloc 去写,须要将所有的事件行为治理起来,便于前期保护

OK,Bloc 的简化模块,Cubit 模式就这样讲完了,对于本人业务写的小我的项目,我就常常用这个 Cubit 去写

最初

  • Bloc 还有很多 Api 针对不同的场景十分的实用,例如:MultiBlocProvider,BlocListener,MultiBlocListener,BlocConsumer 等等,这外面有些 Api 和 Provider 的 Api 是十分类似的,例如 MultiXxxxx,这都是为了缩小嵌套,提供多个全局 Bloc 而提供,大家能够去瞧瞧看,用法也都十分的类似
  • Cubit 范例代码地址

    • Cubit 范例代码
  • Bloc 范例代码地址

    • Bloc 范例代码
  • flutter_bloc 相干 Api 白嫖地址

    • flutter_bloc 相干 Api
  • flutter_bloc

    • GitHub:https://github.com/felangel/bloc
    • Pub:https://pub.dev/packages/flutter_bloc
退出移动版