前言:
- 近来工作较忙,利用了一些晚上放工的时间,终于写完了一个bloc Demo,之前在学习Bloc的时间看了很多文章,固然有很多的文章在说flutter bloc模式的应用,但是百分之八九十的文章都是在说,真正写利用bloc作者开发的flutter_bloc却少之又少。没办法,只能去bloc的github上去找利用方式,末了去bloc官网翻文档。本篇文章侧重讲的是bloc在项目中的利用,以及常见的场景和利用时遇到的题目。
- 针对网络哀求和一些常用工具也举行了封装,写了几个有针对性的页面,做项目的话可以直接拿来用。老例子先上效果。
正文:
flutter_bloc利用将从下图的三个维度阐明
Flutter Bloc(Business Logic Component)是一种基于流的状态管理办理方案,它将应用步调的状态与变乱(也称为操纵)分离开来。Bloc吸取变乱并根据它们来更新应用步调的状态。Bloc通常由三个重要部门构成:变乱(input)、状态(output)和业务逻辑。利用Flutter Bloc,您可以将应用步调分解为不同的模块,从而使其易于维护和扩展。
- Flutter Bloc的焦点概念:
- State:
体现应用步调的状态。它可以是任何范例的对象,例如数字、字符串、布尔值或自界说类。是Bloc提供给外部的数据前言,view层通过state获取bloc内里的数据。
- Event:
体现操纵或变乱,例如按钮按下、API调用或用户输入,常用场景进入页面举行网络数据哀求,就界说一个网络哀求的Event,当用户点击按钮就界说一个点击的Event,然后去bloc内部行止置处罚数据然后通过state回调给view来更新状态。
- Bloc:
通过Event获取外部操纵,在内部处理处罚逻辑接口哀求大概数据处理处罚,然后更新state,通过state把最新数据通报给view革新状态。
BlocProvider:是一个Flutter Bloc提供的小部件,它可以资助我们在整个应用步调中共享和提供Bloc的实例。
- Cubit:
相比bloc省去了Event层,view可以直接举行调用内部方法,同样也是在内部处理处罚逻辑接口哀求大概数据处理处罚,然后更新state,通过state把最新数据通报给view革新状态。
- BlocProvider:
是一个Flutter Bloc提供的小部件,它可以资助我们在整个应用步调中共享和提供Bloc的实例。通俗来讲就是完成context和bloc对象的绑定,在我们必要用到bloc的时间,通过context就可以拿到bloc对象。BlocProvider利用的机遇很紧张,稍有不慎就会报错,下面会说。
- MultiBlocProvider
重要的利用场景就是在main方法中,绑定多个context和bloc对象,一样寻常绑定的是在App一启动就必要展示处理处罚逻辑的页面。
- BlocBuilder:
是一个Flutter Bloc提供的小部件,它会在状态发生变化时主动重修,并用于构建页面。通俗来讲就是,当state对象内部的值发生变化时,BlocBuilder会主动重现构建来革新widget。还用一个很紧张的方法buildWhen:就是可以通过stateh大概view内里的其他属性来判断页面是否必要重新举行构建。
- BlocListener:
监听bloc内里的状态,通过也是通过state举行回调,来实验某个变乱,比如说关照革新或界面跳转...内里也有一个紧张的方法listenWhen:可以有选择性的举行监听。
- BlocConsumer:
BlocBuilder和BlocListener聚合体,既有构立功能又有监听功能。内里有builder listener buildWhen listenWhen四个方法,也很常用。
- 利用 Bloc 和 cubit 开发一个页面完整流程。
- bloc模式:
1.创建类,天生bloc类和样板代码,这里bloc官方提供的有插件,在Android Studio安装利用即可,不在多说。
2.绑定bloc和context,利用BlocProvider
BlocProvider<NovelDetailNavBloc>( create: (BuildContext context) => NovelDetailNavBloc(), child: NovelDetailPage( imageUrl: imageUrl, ), )3.界说Event
/// 获取数据class GetNovelDetailEvent extends NovelDetailEvent { GetNovelDetailEvent(this.mainPath, this.seriesPath, this.recommendPath); final String mainPath; final String seriesPath; final String recommendPath;}4.界说State
class NovelDetailState extends BaseState { CartoonModelData? mainModel; List<CartoonRecommendDataInfos>? recommendList; List<CartoonSeriesDataSeriesComics>? seriesList; NovelDetailState init() { return NovelDetailState() ..netState = NetState.loadingState ..mainModel = CartoonModelData() ..recommendList = [] ..seriesList = []; } NovelDetailState clone() { return NovelDetailState() ..netState = netState ..mainModel = mainModel ..recommendList = recommendList ..seriesList = seriesList; }}5.在Bloc处理处罚逻辑,并更新state发送更新关照
NovelDetailBloc() : super(NovelDetailState().init()) { on<GetNovelDetailEvent>(_getNovelDetailEvent); } Future<void> _getNovelDetailEvent(event, emit) async { XsEasyLoading.showLoading(); /// 主数据 ResponseModel? responseModel = await LttHttp().request<CartoonModelData>(event.mainPath, method: HttpConfig.mock); /// 同系列数据 ResponseModel? responseModel2 = await LttHttp().request<CartoonSeriesData>(event.seriesPath, method: HttpConfig.mock); /// 保举数据 ResponseModel? responseModel3 = await LttHttp().request<CartoonRecommendData>(event.recommendPath, method: HttpConfig.mock); XsEasyLoading.dismiss(); state.mainModel = responseModel.data; CartoonSeriesData cartoonSeriesData = responseModel2.data; state.seriesList = cartoonSeriesData.seriesComics; CartoonRecommendData cartoonRecommendData = responseModel3.data; state.recommendList = cartoonRecommendData.infos; state.netState = NetState.dataSuccessState; emit(state.clone()); }6.在view中搭建UI,通过state完成赋值操纵。
Widget buildPage(BuildContext context) { return BlocConsumer<BlocStaggeredGridViewBloc, StaggeredGridViewState>( listener: _listener, builder: (context, state) { return resultWidget(state, (baseState, context) => mainWidget(state), refreshMethod: () { _pageNum = 1; _getData(); }); }, ); }完成上面几步,就根本玩成了一个网络列表的开发。
再联合这张官方图,有助于快速调解思绪。Demo
- cubit模式:
cubit模式和bloc的不同就是省去了Event层,其他的用法都是一样,Demo中有具体的例子。
这就就不贴代码了。还是联合官方图,可以快速明白。
- buildWhen:
在现实的开发工作中,并不是每次state内里的属性发生变化都必要build页面,这个时间就必要buildWhen了.
- 利用场景
登录注册,登录时有两个输入框,一个输入手机号码,一个输入暗码,那么当输入手机号码的时间,只必要革新手机号码的widget,输入暗码时,只必要革新暗码的widget,那么这种场景就必要buildWhen来实现。首先来看一下buildWhen的内部实现
/// Signature for the `buildWhen` function which takes the previous `state` and/// the current `state` and is responsible for returning a [bool] which/// determines whether to rebuild [BlocBuilder] with the current `state`.typedef BlocBuilderCondition<S> = bool Function(S previous, S current);大致意思就是该方法返回两个state,根据之前的state和 当前的state来判断是否必要革新当前的widget,看到这里这种场景就很好实现了。代码如下:Demo
buildWhen: (previous, current) { if (type == 1) { return previous.phoneNumber != current.phoneNumber; } else { return previous.codeNumber != current.codeNumber; } },
- 长处
镌汰每次build树的范围和次数,极大的提拔了性能。也是颗粒化革新的一种常用方式。
- 实现原理
底层利用provider的Select来实现的,下篇文章会侧重讲一个Select.
- listenWhen:
当在bloc大概cubit中举行网络哀求大概数据处理处罚时,通常widget必要根据处理处罚效果去实验某些变乱,这时间就必要利用listen了。
- 利用场景
在bloc大概cubit中网络哀求乐成后,在 widget中,必要相应的竣事下拉革新大概上拉加载大概展示没有更多数据了,这时间在widget中利用BlocListenr大概BlocConsumer,然后实现listen监听方法即可,但是最高效的利用listernWhen来实现,由于现实的开发当中,bloc大概cubit中会处理处罚很多的逻辑,比如处理处罚点赞大概收藏逻辑时,就不必要widget内里处理处罚竣事下拉革新等变乱了,只必要build页面即可。以是这种场景最好利用listernWhen了。
listener: _listener, listenWhen: (state1, state2) { if (state1.netLoadCount != state2.netLoadCount) { return true; } return false; },在state中,界说一个属性netLoadCount,只有当前state的netLoadCount和上一个state的netLoadCount不同等时才会监听,才会去实验变乱。
- 颗粒化革新或局部革新:
- 例子1
利用buildWhen来实现,就是上面实现登录注册页面的逻辑,不在多说。
- 例子2
以本Demo中的这个页面为例,首先来说,这个页面全部的数据都是网络哀求而来,然后页面往上滑动时,根据滑动隔断来改变导航栏的透明度和页面变化。那么就是当一开始进入页面,举行网络哀求,然后build页面,当滑动页面时,只必要build导航栏widget就可以了,由于除了导航栏变化,别的都没有变化,没有须要从此页面的根节点举行革新。
- 代码实现方案1两个Bloc实现):
当滑动ListView时,页面会在此BlocBuilder下全部都会革新,然而,我们在滑动ListView时,只必要革新导航栏widget,以是,可以再创建一个NavBloc NavState NavEvent了,导航栏widget用新的导航栏的BlocBuilder包裹,当滑动ListView时,更新新创建的NavState如许导航栏widget就革新了,而根节点的state并没有改变,以是团体页面不会重新build如许就实现了局部革新。
- 代码实现方案2一个Bloc实现):
利用buildWhen来实现,BlocBuilder不放在page的根节点,滑动视图ListView和NavWidget分别用同一个BlocBuilder来包裹,根据不同的条件来选择重新build这两个widget.在本Demo中`有案例实现可自行查察.
- 单页面多网络哀求实现思绪
- 思绪1
界说一个bloc大概cubit,利用一个BlocBuilder,BlocBuilder放在页面根节点.全部接口串行处理处罚,等数据全部哀求乐成,更新state,调用emit()方法,革新页面。loading时间会长,体验不是很好。
- 思绪2
界说一个bloc大概cubit,全部的接口并行处理处罚,末了利用Future.wait来组合数据。loading时间短,体验好,留意非常逻辑处理处罚。具体利用那种思绪来实现,具体业务具体分析吧,本Demo中两种思绪都有实现。
- 针对bloc特性 封装网络哀求
利用bloc多了,就会发如今event中假如如许哀求网络会报错。代码如下:
https().updateData(params, onSuccess: (data) { emit(); });之前遇到过如许的题目,具体的报错信息就不贴了,bloc抛出的大致意思就是event方法是从上往下同步次序实验的,以是当onSuccess异步回调时,这个event方法现实已经被斲丧掉了,以是就报错了。这是bloc模式下event的题目,在cubit模式下,没有此题目,可以放心大胆的写。为了在项目中利用方便,制止堕落,网络同一封装成了如许,在哪种模式下都没有题目。
ResponseModel? responseModel = await LttHttp().request<CartoonModelData>(event.mainPath, method: HttpConfig.get);
- 网络哀求封装思绪
返回值用通用的ResponseModel来继续,内里有code message <T>data 方便根据不同的code值举行不同处理处罚逻辑,然后request方法必要传入一个泛型T,传入的这个泛型T,就是返回值ResponseModel的data,可能思绪有点绕,看看代码就明白了。如许把 json解析啥的都放在网络内里行止置处罚了,很方便。
await LttHttp().request<CartoonModelData>(event.mainPath, methodHttpConfig.get);state.mainModel = responseModel.data;
- json转model
利用FlutterJsonBeanFactory插件来完成,利用方便,教程可以自行百度。利用时要留意引入别的model时,用绝对路径还是相对路径的题目。
- BasePage筹划
通例筹划吧,满意一样寻常开发利用,属性如下。
/// 是否渲染buildPage内容 bool _isRenderPage = false; /// 是否渲染导航栏 bool isRenderHeader = true; /// 导航栏颜色 Color? navColor; /// 左右按钮横向padding final EdgeInsets _btnPaddingH = EdgeInsets.symmetric(horizontal: 14.w, vertical: 14.h); /// 导航栏高度 double navBarH = AppBar().preferredSize.height; /// 顶部状态栏高度 double statusBarH = 0.0; /// 底部安全区域高度 double bottomSafeBarH = 0.0; /// 页面配景致 Color pageBgColor = const Color(0xFFF9FAFB); /// header体现页面title String pageTitle = ''; /// 是否允许某个页iOS滑动返回,Android物理返回键返回 bool isAllowBack = true; bool resizeToAvoidBottomInset = true; /// 是否允许点击返回上一页 bool isBack = true;
项目内里全部的 state都继续于 BaseState为啥要如许做??
由于在开发一个页面必要根据网络返回的状态来判断体现正常页面 空数据页面 网络报错页面等等,也就是说页面的体现状态是由state来控制的,那么这些代码肯定不可能,新创建一个页面就写一堆判断,这些判断通过把BaseState交给BasePage来实现。
/// BaseState/// 项目中全部必要根据网络状态体现页面的state必须继续于BaseStateenum NetState { /// 初始状态 initializeState, /// 加载状态 loadingState, /// 错误状态,体现失败界面 error404State, /// 错误状态,体现革新按钮 errorShowRefresh, /// 空数据状态 emptyDataState, /// 加载超时 timeOutState, /// 数据获取乐成状态 dataSuccessState,}abstract class BaseState { /// 页面状态 NetState netState = NetState.loadingState; /// 是否尚有更多数据 bool? isNoMoreDataState; /// 数据是否哀求完成 bool? isNetWorkFinish; /// 数据源 List? dataList; /// 网络加载次数 用这个属性判断 BlocConsumer 是否必要监听革新数据 int netLoadCount = 0;}
- 思绪
在bloc大概 cubit中通过网络返回ResponseModel中的code来给state赋值,在widget中,将state传给BasePage,终极BasePage会根据state返回一个界面准确的展示效果。
处理处罚网络层根据 ResponseModel 给state改变状态代码
class HandleState { static handle(ResponseModel responseModel, BaseState state) { if (responseModel.code == 100200) { if ((state.dataList ?? []).isEmpty) { state.netState = NetState.emptyDataState; } else { state.netState = NetState.dataSuccessState; } } else if (responseModel.code == 404) { state.netState = NetState.error404State; } else if (responseModel.code == -100) { state.netState = NetState.timeOutState; } else { state.netState = NetState.errorShowRefresh; } }}widget中build代码
@override Widget buildPage(BuildContext context) { return BlocConsumer<MessageModuleCubit, MessageModuleState>( listener: _listener, listenWhen: (state1, state2) { if (state1.netLoadCount != state2.netLoadCount) { return true; } return false; }, builder: (context, state) { return resultWidget(state, (baseState, context) => mainWidget(state), refreshMethod: () { _pageNum = 1; _getData(); }); }, ); }BasePage 中处理处罚代码
Widget resultWidget(BaseState state, BodyBuilder builder, {Function? refreshMethod}) { if (state.netState == NetState.loadingState) { return const SizedBox(); } else if (state.netState == NetState.emptyDataState) { return emptyWidget('暂无数据'); } else if (state.netState == NetState.errorShowRefresh) { return errorWidget('网络错误', refreshMethod ?? () {}); } else if (state.netState == NetState.error404State) { return net404Widget('页面404了'); } else if (state.netState == NetState.initializeState) { return emptyWidget('NetState 未初始化,请将状态置为dataSuccessState'); } else if (state.netState == NetState.timeOutState) { return timeOutWidget('加载超时,请重试~', refreshMethod ?? () {}); } else { return builder(state, context); } }别的,全部的非常视图都支持在widget中重写,假如有特殊环境样式的展示,直接重写即可。
利用的是fluro,利用人数和点赞量很高,也比力好用,就不多说了。
为了更高效的开发,Demo内里封装了常用widget的封装,比如BaseListView BaseGridView等等,代码写起来简直不要太爽!
竣事:
就写到这里吧,针对于Bloc的项目架构筹划已经可以了,不停以为,技能就是用来沟通的,没有沟通就没有上进,在此,接待各种大佬吐槽沟通。Coding不易,假如感觉对您有些许的资助,接待点赞批评。
声明:
仅开源供各人学习利用,克制从事商业运动,如出现齐备法律题目自行负担!!!
仅学习利用,如有侵权,造成影响,请接洽本人删除,谢谢
项目所在 |