Flutter圈子Flutter

Flutter Bloc搭建通用项目架构

2023-09-18  本文已影响0人  木子雨廷t

前言:

cubit-list bloc-grid bloc-stagger

正文:

flutter_bloc使用将从下图的三个维度说明


image

Flutter Bloc(Business Logic Component)是一种基于流的状态管理解决方案,它将应用程序的状态与事件(也称为操作)分离开来。Bloc接收事件并根据它们来更新应用程序的状态。Bloc通常由三个主要部分组成:事件(input)、状态(output)和业务逻辑。使用Flutter Bloc,您可以将应用程序分解为不同的模块,从而使其易于维护和扩展。

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();
        });
      },
    );
  }

完成上面几步,就基本玩成了一个网络列表的开发。

bloc模式
再结合这张官方图,有助于快速调整思路。Demo

在实际的开发工作中,并不是每次state里面的属性发生变化都需要build页面,这个时候就需要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;
        }
      },

当在bloc或者cubit中进行网络请求或者数据处理时,往往widget需要根据处理结果去执行某些事件,这时候就需要使用listen了。

listener: _listener,
      listenWhen: (state1, state2) {
        if (state1.netLoadCount != state2.netLoadCount) {
          return true;
        }
        return false;
      },

state中,定义一个属性netLoadCount,只有当前state的netLoadCount上一个state的netLoadCount不一致时才会监听,才会去执行事件。

具体使用那种思路来实现,具体业务具体分析吧,本Demo中使用的思路1来实现的。

使用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);
  await LttHttp().request<CartoonModelData>(event.mainPath, methodHttpConfig.get);
state.mainModel = responseModel.data;

常规设计吧,满足日常开发使用,属性如下。

/// 是否渲染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必须继承于BaseState
enum 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;
}

处理网络层根据 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不易,如果感觉对您有些许的帮助,欢迎点赞评论。

声明:

仅开源供大家学习使用,禁止从事商业活动,如出现一切法律问题自行承担!!!

仅学习使用,如有侵权,造成影响,请联系本人删除,谢谢
项目地址

上一篇 下一篇

猜你喜欢

热点阅读