Flutter Bloc搭建通用项目架构

计算机软件开发 2024-10-6 03:53:36 33 0 来自 中国
前言:


  • 近来工作较忙,利用了一些晚上放工的时间,终于写完了一个bloc Demo,之前在学习Bloc的时间看了很多文章,固然有很多的文章在说flutter bloc模式的应用,但是百分之八九十的文章都是在说,真正写利用bloc作者开发的flutter_bloc却少之又少。没办法,只能去bloc的github上去找利用方式,末了去bloc官网翻文档。本篇文章侧重讲的是bloc在项目中的利用,以及常见的场景和利用时遇到的题目。
  • 针对网络哀求和一些常用工具也举行了封装,写了几个有针对性的页面,做项目的话可以直接拿来用。老例子先上效果。
1.gif 2.gif 正文:

flutter_bloc利用将从下图的三个维度阐明

4.jpg

  • 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();        });      },    );  }完成上面几步,就根本玩成了一个网络列表的开发。

5.jpg
再联合这张官方图,有助于快速调解思绪。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

    7.gif
    以本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;

  • BaseState筹划
项目内里全部的  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,利用人数和点赞量很高,也比力好用,就不多说了。

  • 各种base类的筹划
为了更高效的开发,Demo内里封装了常用widget的封装,比如BaseListView BaseGridView等等,代码写起来简直不要太爽!
竣事:

就写到这里吧,针对于Bloc的项目架构筹划已经可以了,不停以为,技能就是用来沟通的,没有沟通就没有上进,在此,接待各种大佬吐槽沟通。Coding不易,假如感觉对您有些许的资助,接待点赞批评。
声明:

仅开源供各人学习利用,克制从事商业运动,如出现齐备法律题目自行负担!!!
仅学习利用,如有侵权,造成影响,请接洽本人删除,谢谢
项目所在
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-11-22 02:04, Processed in 0.171943 second(s), 36 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表