转载请注明出处王亟亟的大牛之路

有一段时间没有写东西了,然后之前1年多时间一直在做RN相关的业务和一些新技术的调研,外加“沉迷炒币”写文章也就很少了,然后最近因为一些公司框架的调整所以对部分技术做了一些迁移,然后一个比较重要的思路就是用BLOC处理状态机的业务,将肆意安放的的setState()MVVM起来,然后把写sample的过程分享一下

什么是Bloc ?

网上有很多铺天盖地的解释啊说明,大多数也是抄来抄去的,这里也就不炒冷饭复制粘贴了,想了解理论知识可以自行google

源码地址

https://github.com/ddwhan0123/MaiMai/tree/blog_bloc
最近的一些整理包括轮子都会在这个仓库里做,然后某篇文章会单独的拉一个分支来锁版本
本文对应的分支是 blog_bloc


实现效果


目录结构

设计思维源于官方sample 把xxx_state.dart(数据/状态本身)
xxx_event(事件流/状态变化的诱因)
xxx_bloc.dart(使二者关联且产生逻辑变更的地方)


业务场景

  1. 点位数值整体覆盖 加
  2. 点位数值整体覆盖 减
  3. 点位归零 重置
  4. 点位数值加 部分值添加

依赖:

 flutter_bloc: 2.0.1equatable: ^1.0.0

equatable :

能够在Dart比较对象通常涉及必须重写==运算符以及hashCode .

flutter_bloc :

谷歌官方的实现,内部通过 Stream,Sink,BehaviorSubject实现(可自行百度,资料比较全)


sample_bloc.dart 状态的承载类

一个很简单的普通对象 甚至没有继承链

构造函数在初始化状态机时调用(主要为了初始化bloc的state值)

class PointState {int x;int y;int z;//构造函数PointState(this.x, this.y, this.z);//重置操作调用factory PointState.reset() {return PointState(0, 0, 0);}//部分值修改时调用update(int x, int y, int z) {return PointState(x, y, z);}
}

counter_event.dart 事件类


import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';import 'counter_state.dart';abstract class CounterEvent extends Equatable {const CounterEvent();
}//重置数据 无需逻辑行为或返回参数,纯粹当指令用
class ButtonPressReset extends CounterEvent {@overrideList<Object> get props => null;
}//点位增加 传入某个值,做更新操作
class ButtonPressAddX extends CounterEvent {final int x;const ButtonPressAddX({@required this.x,});@override// TODO: implement propsList<Object> get props => [x];@overrideString toString() {return 'ButtonPressAddX { x: $x }';}
}//点位数值整体重置,所以传入整个状态对象, event 本身不对值进行篡改
class ButtonPressedAdd extends CounterEvent {final PointState point;const ButtonPressedAdd({@required this.point,});@overrideList<Object> get props => [point];@overrideString toString() => getString();String getString() {var x = point.x;var y = point.y;var z = point.z;return 'ButtonPressedAdd { x: $x, y: $y , z: $z }';}
}class ButtonPressedReduce extends CounterEvent {final PointState point;const ButtonPressedReduce({@required this.point,});@overrideList<Object> get props => [point];@overrideString toString() => getString();String getString() {var x = point.x;var y = point.y;var z = point.z;return 'ButtonPressedReduce { x: $x, y: $y , z: $z }';}
}

sample_bloc.dart 核心处理类(也是和页面产生关联的 view module)


class CounterBloc extends Bloc<CounterEvent, PointState> {var point;CounterBloc({this.point});//初始化 状态机 如果没传参 就构建一个(0,0,0)的对象@overridePointState get initialState => point != null ? point : PointState(0, 0, 0);@overrideStream<PointState> mapEventToState(CounterEvent event) async* {PointState currentState = PointState(state.x, state.y, state.z);if (event is ButtonPressedReduce) {currentState.x = state.x - 1;currentState.y = event.point.y;currentState.z = state.z - 1;yield currentState;} else if (event is ButtonPressedAdd) {currentState.x = state.x + 1;currentState.y = event.point.y;currentState.z = state.z + 1;yield currentState;//重置状态} else if (event is ButtonPressReset) {yield PointState.reset();//单点位数值增加} else if (event is ButtonPressAddX) {yield state.update(event.x, currentState.y, currentState.z);}}
}

继承/实现Bloc<Event,State> ,必须实现initialStatemapEventToState方法

Bloc的构造函数里就会调用 initialState方法,这个方法需要由子类实现,它的类型就是Bloc<Event, State>中的State的类型,并且会调用_bindStateSubject()

  Bloc() {_stateSubject = BehaviorSubject<State>.seeded(initialState);_bindStateSubject();}

_stateSubject是 BehaviorSubject<State>类型的一个对象,BehaviorSubject是一个广播流,它能记录下最新一次的事件,并在新的收听者收听的时候将记录下的事件作为第一帧发送给收听者。当然这并不是dart基础库实现的,他使用了rxdart。(这一部分的知识可查看传送门)

在每次新的事件流触发的时候都会循环处理前后两次事物的对象(也就是我们的State实例),如果两个对象相等并且订阅已经结束则不处理,反之会对这个对象进行二次包装。

所以我们在mapEventToState方法里需要返回的是经过业务逻辑处理完的nextState
在该方法内我们可以拿到 事件类型也就是 event对象 根据event 对象(可传参,也可不传),以及原状态state
也就是当前订阅序列给到的值 State get state => _stateSubject.value;就可以构建我们的新状态了

例子中 全量替换的几种实现方式就是不传参的。而单点数值修改的方式就是从event中进行了取值做了处理,然而实现业务逻辑的方法还是写在了State里,是因为可以很方便的获取State对象中的本地属性,当然在实际场景里还可以做二次分离。

  void _bindStateSubject() {Event currentEvent;transformStates(transformEvents(_eventSubject, (Event event) {currentEvent = event;return mapEventToState(currentEvent).handleError(_handleError);})).forEach((State nextState) {if (state == nextState || _stateSubject.isClosed) return;final transition = Transition(currentState: state,event: currentEvent,nextState: nextState,);try {BlocSupervisor.delegate.onTransition(this, transition);onTransition(transition);_stateSubject.add(nextState);} on Object catch (error) {_handleError(error);}},);}

业务端调用

首先需要你要让状态机,bloc,state,event互相关联不调用setState()方法,就需要把你的状态树包裹在BlocProviderBlocBuilder中(代码偏多,不直接贴了,只贴重要的部分,和源码有所差异)

BlocProvider在原来 StatefulWidget 的 child 外面再包了一个 InheritedWidget, I在查找符合指定类型的 ancestor 时,就可以调用 InheritedWidget 的实例方法 context.ancestorInheritedElementForWidgetOfExactType(),而这个方法的时间复杂度是 O(1),意味着几乎可以立即查找到满足条件的 ancestor。

BlocBuilder做了很好的响应处理,builder返回了上下文对象和我们所需的状态属性。

 @overrideWidget build(BuildContext context) {return MaterialApp(home: BlocProvider(builder: (context) => CounterBloc(),child: CounterPage(),));}Widget CounterPage(){var a=BlocBuilder<CounterBloc, PointState>(builder: (context, point) {return Column(children: <Widget>[Text(point.x.toString(),style: TextStyle(fontSize: 24.0),),Text(point.y.toString(),style: TextStyle(fontSize: 24.0),),Text(point.z.toString(),style: TextStyle(fontSize: 24.0),)],);},)return a
}

总结:

bloc 处理事件和状态
state 纯粹的状态实体
event 业务场景的事件
三者配合bloc+flutter_bloc优秀的剔除了人工手动维护 setState()所造成的重绘问题

相关资料

https://github.com/felangel/bloc
https://github.com/lizubing1992/flutter_bloc
https://github.com/dragonetail/flutterpoc/tree/counter_refactor

Flutter Bloc构建轻量级MVVM相关推荐

  1. Flutter BLoC 异步通信、BlocBuilder的基本使用、BlocProvider的初探

    题记 -- 执剑天涯,从你的点滴积累开始,所及之处,必精益求精. Flutter是谷歌推出的最新的移动开发框架. [x1]微信公众号的每日提醒 随时随记 每日积累 随心而过 [x2]各种系列的视频教程 ...

  2. Flutter Bloc 官方文档(BlocBuilder翻译)

    什么是Bloc,为什么用Flutter Bloc 就不介绍了,直入主题. 直接看官网吧,官网已经全部翻译.传送门点我 Bloc Widgets BlocBuilder BlocBuilder 是一个F ...

  3. Flutter BLoC 用户登录

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XSljASWk-1620260140025)(https://ducafecat.tech/2021/05/06/tra ...

  4. 如何构建Android MVVM 应用框架

    本文转载自[http://tech.meituan.com/android_mvvm.htmlhmsr=toutiao.io&utm_medium=toutiao.io&utm_sou ...

  5. EditPlus构建轻量级编译环境

    你还在为机子跑VS,VC6.0吃力而苦恼吗?最近发现网上一个小窍门,就是用EditPlus文本编辑器构建轻量级编译环境, 轻量级的东西就是受人喜欢,要不说Spring能流行起来呢.最近自学C#,有了J ...

  6. flutter bloc 实例

    为了便于理解flutter bloc 我们先看看他是怎么用的 我们先定义相关的bloc test_bloc.dart import 'package:flutter_bloc/flutter_bloc ...

  7. 使用BLoC 构建 Flutter的页面实例

    前言 我们上一篇讲了 BlocProvider 的使用,感觉和 Provider 几乎是一样的,没什么新鲜感.在上一篇中有一个 BlocBuilder 倒是有点奇怪,我们回顾一下代码: BlocBui ...

  8. android mvp mvvm ppt,还在用 MVP?快来试试 MVVM! Relight:轻量级 MVVM 框架

    优势 稳定 减少内存泄漏:新手很容易在线程切换的地方写出导致内存泄漏的代码,但如果把线程切换交给框架来做,出错的概率就大大降低. 减少 crash:根据我的开发经历,大部分 crash 都是空指针导致 ...

  9. Flutter第一部分(UI)第二篇:在Flutter中构建布局

    前言:Flutter系列的文章我应该会持续更新至少一个月左右,从User Interface(UI)到数据相关(文件.数据库.网络)再到Flutter进阶(平台特定代码编写.测试.插件开发等),欢迎感 ...

最新文章

  1. docker容器网络 - 同一个host下的容器间通信
  2. 面对万亿级测序市场,纳米孔测序技术何去何从?
  3. OpenFire源码学习之二十五:消息回执与离线消息(下)
  4. springboot打包时加入本地jar打包
  5. colab如何通过<>来直接加入相对应的代码段呢?模块化代码操作,真好
  6. AlldayTest 产品使用--文件
  7. docker-compose的介绍与安装(结合官方文档)
  8. 结婚虽易,终老不易:EntityFramework和AutoMapper的婚后生活
  9. linux下进程的创建代码,伪代码说明Linux进程创建过程
  10. 关于iOS 6 中的一些“xxxxxx” is deprecated 问题的解决办法
  11. paip.sql2008 客户端软件绿色版V319
  12. 5.4 continue,break跳出循环
  13. 透镜成像、眼球成像、小孔成像原理
  14. Ubuntu没有屏幕亮度调节怎么调整屏幕亮度?
  15. Word Maze(单词迷宫)
  16. 注册电子邮箱,打造个人网络商务形象
  17. 开闭原则 by Robert Martin
  18. Word中截取部分内容并保存为jpg图片的方法
  19. java全栈系列之JavaSE-面向对象(方法的定义与调用)030
  20. 剑指Offer49—丑数

热门文章

  1. 端口数和最大连接数的关系
  2. 7-18 Decimal Equivalent of a Binary Number (10 分)
  3. hdu6595 概率和期望
  4. iOS开发中MD5加密算法的实现
  5. XDOJ 172-构造表达式
  6. 有没有这样的后浪,月薪3000
  7. 复利计算器(软件工程)及Junit测试———郭志豪
  8. rasp agent_Rasp Pi上的Perf机器学习
  9. 新手SEO需要知道的SEO几个步骤
  10. 程序设计思维模测 - M4