当前位置: 首页 > 开发杂谈 >

【第九天 - Flutter Bloc+Cubit 架构教学】

今日的程序码

Bloc 范例 => GITHUB Bloc
Cubit 范例 => GITHUB Cubit

什麽事 Bloc ㄋ?




flutter_bloc 文件

我们来看这张图,简单来说,他就是 UI 触发一个事件,传到了 bloc 後,处理逻辑,处理好後,丢出一个状态。当 UI 接收到对应的状态,给予对应的 UI。

Bloc 的种类

Bloc 这个套件有两种写法。

  • Bloc
  • Cubit(flutter_bloc version 5.0以上才有的)。

差异

  • Bloc 当触发新的状态, blocBuilder 会重新建立。
    • Bloc 需要有 event、bloc、state 三种组成,yield 是作为输出状态的指令。
  • Cubit 当触发新的状态, blocBuilder 会重新建立。
    • cubit 则是需要 cuibt 里面的 function、state 两种组成,emit 作为输出状态

Cubit

Cubit 的 State(状态)(Cubit、Bloc 程序码一样)

这边一般会先用一个 IPostEvent 然後後面的继承他这样子。前面加上一个 I 是因为这样我比较好知道他是 abstract class。

  • PostLoading 的状态
  • PostSuccess 的状态。(里面的参数是用来显示在画面上的,因此我需要 postList 这笔资料。)

Equatable 在前几 Day 7 有讲过。

abstract class IPostState {}

class PostLoading extends IPostState {}

class PostSuccess extends IPostState {
  final List<PostModel> postList;
  PostSuccess( this.postList);
}

直接来看 Cubit 逻辑范例

  • 这边定义了排序的状态(SortState)。
  • PostCubit 丢了一个建构子,是一个 IPostRepository,用来请求资料的。super 是用来设定初始值的。(这边预设的状态是在 PostLoading)。
  • emit 代表,输出状态。预设一刚开始就是 PostLoading 因此,在 fetchDatasort 结束後,状态将会是 PostSuccess
enum SortState { userId, id, title, body }

class PostCubit extends Cubit<IPostState> {
  IPostRepository _repository;
  List<PostModel> postList = [];

  PostCubit(IPostRepository repository)
      : _repository = repository,
        super(PostLoading());

  Future<void> fetchData() async {
    postList = await _repository.fetchData();
    sort(SortState.id);
  }

  void sort(SortState sortBy) {
    switch (sortBy) {
      case SortState.userId:
        postList.sort((a, b) => a.userId.compareTo(b.userId));
        break;
      case SortState.id:
        postList.sort((a, b) => a.id.compareTo(b.id));

        break;
      case SortState.title:
        postList.sort((a, b) => a.title.compareTo(b.title));
        break;
      case SortState.body:
        postList.sort((a, b) => a.body.compareTo(b.body));
        break;
    }
    emit(PostSuccess([...postList]));
  }
}

Cubit 初始化(Cubit、Bloc 使用方式一样)

Cubit 的初始化和後面会讲到的 Bloc 一样,和 Provider 类似。
MultiBlocProvider 元件是一个初始化的共享装置元件。
这边我们在 MaterialApp 外面建立了一个 MultiBlocProvider,代表只要在 MaterialApp 里面都可以取到 PostCubit 的资料。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<PostCubit>(
          create: (BuildContext context) => PostCubit(
            PostRepository(PostService()),
          ),
        ),
      ],
      child: MaterialApp(
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Cubit Sample'),
      ),
    );
  }
}

Cubit 专用的触发事件(Event)

Cubit 的触发事件不会向 bloc 有一个专门的 Event,而是直接呼叫 PostCubit 里面的 sort() 方法。

context.read<PostCubit>().sort(value);

BlocBuilder(Cubit、Bloc 使用方式一样)

CubitBlocBuilderBloc 一样

BlocBuilder<PostCubit, IPostState>(
    builder: (_, state) {
      // 如果种态势 PostSuccess 的话
      if (state is PostSuccess) {
        return Widget;
      }
      // else{} //预设值。
      return Center(child: CircularProgressIndicator());
    },
  );

BlocListener(Cubit、Bloc 使用方式一样)

这边引用 flutter_bloc 文件范例

BlocBuilder<BlocA, BlocAState>(
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)
// or
// 你不想要共享资料的话,可以放在这。
BlocBuilder<BlocA, BlocAState>(
  bloc: blocA, // provide the local bloc instance
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

BlocBuilder 和 BlocListener 的差异

我会这样说,BlocBuilder 只是用来回应不同状态下对应的 widgets,BlocListener 则是用来在不同状态 "do things "。比方说跳页showSnackBar 等等...,你可以考范例 showsnackbarNavigator

BlocBuilder (_MyListView)范例

如果状态是 PostSuccess,那麽我就回传一个 ListView.builder,其他的则是回传一个 CircularProgressIndicator

class _MyListView extends StatelessWidget {
  const _MyListView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<PostCubit, IPostState>(
      builder: (_, state) {
        if (state is PostSuccess) {
          return ListView.builder(
            itemCount: state.postList.length,
            itemBuilder: (context, index) {
              PostModel item = state.postList[index];
              return Container(
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.all(Radius.circular(16)),
                      color: Colors.white,
                      border: Border.all(color: Colors.blueAccent, width: 2.0)),
                  margin: EdgeInsets.all(8),
                  padding: EdgeInsets.all(8),
                  child: RichText(
                    text: TextSpan(
                      style: DefaultTextStyle.of(context).style,
                      children: <TextSpan>[
                        TextSpan(
                          text: item.id.toString() + ". " + item.title,
                          style: TextStyle(fontSize: 18, color: Colors.red),
                        ),
                        TextSpan(
                          text: '\n' + item.body,
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        TextSpan(
                          text: "\nUser ID:" + item.userId.toString(),
                          style: TextStyle(fontSize: 18),
                        ),
                      ],
                    ),
                  ));
            },
          );
        }
        return Center(child: CircularProgressIndicator());
      },
    );
  }
}

MyHomePage 的程序码

class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    context.read<PostCubit>().fetchData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          actions: <Widget>[
            PopupMenuButton<SortState>(
              icon: Icon(Icons.more_vert),
              itemBuilder: (context) => [
                PopupMenuItem(
                  child: Text('使用 userId 排序'),
                  value: SortState.userId,
                ),
                PopupMenuItem(
                  child: Text('使用 id 排序'),
                  value: SortState.id,
                ),
                PopupMenuItem(
                  child: Text('使用 title 排序'),
                  value: SortState.title,
                ),
                PopupMenuItem(
                  child: Text('使用 body 排序'),
                  value: SortState.body,
                )
              ],
              onSelected: (SortState value) {
                context.read<PostCubit>().sort(value);
              },
            )
          ],
        ),
        body: _MyListView());
  }
}

Bloc

Bloc 则是把事件分开需要有 EventBlocState 作为事件。
这边顺便介绍一个 Android Studio Bloc 套件

post_bloc_event(Evnet,触发事件,Bloc 专用的)

part of 'post_bloc.dart';
abstract class IPostEvent {}
/// cal Api 的事件
class FetchPostData extends IPostEvent {}
/// 排序的事件
class SortPostEvent extends IPostEvent {
  final SortState sortState;
  SortPostEvent({required this.sortState});
}

post_bloc(Bloc,处理逻辑的部份)

enum SortState { userID, id, title, body }

class PostBloc extends Bloc<IPostEvent, IPostState> {
  final IPostRepository _repository;

  List<PostModel> postList = [];
  /// 设定初始状态 super(这里要放初始状态)
  PostBloc({required IPostRepository repository}) : _repository = repository,super(PostLoading());

  @override
  Stream<IPostState> mapEventToState(IPostEvent event) async* {
    // 如果是件事 FetchPostData
    if (event is FetchPostData) {yield* _fetchData(event);}
    // 如果是件事 SortPostEvent
    else if(event is SortPostEvent){yield* _sortState(event);}
  }

  Stream<IPostState> _fetchData(FetchPostData event) async* {
    // 请求 API
    postList = await _repository.fetchData();
    // 触发另一个排序事件 SortPostEvent
    add(SortPostEvent(sortState: SortState.id));
  }
  // 自定义的 method
  Stream<IPostState> _sortState(SortPostEvent event) async* {
    _sort(event.sortState);
    yield PostSuccess(postList);
  }
  // sort method
  Future<void> _sort(SortState sortState)async {
    switch (sortState) {
      case SortState.title:
        postList.sort((a, b) => a.title.compareTo(b.title));
        break;
      case SortState.id:
        postList.sort((a, b) => a.id.compareTo(b.id));
        break;
      case SortState.userID:
        postList.sort((a, b) => a.userId.compareTo(b.userId));
        break;
      case SortState.body:
        postList.sort((a, b) => a.body.compareTo(b.body));
        break;
    }
  }
}

State

这边的 StateCubit 一样。
State 的程序码 => GITHUB

初始化设定

这边初始化设定和 Cubit 一样。
初始化的程序码 => GITHUB

触发事件 Event

bloc 是使用 add() 里面放 event 的 class

/// cubit
 context.read<PostCubit>().sort(value);
/// bloc
 context.read<PostBloc>().add(SortPostEvent(sortState: value));

相关文章:

  • 外贸人如何对外贸网站进行优化
  • 亚马逊获评Feedback和Review的对比与区别
  • 品牌卖家最常犯的一些错误
  • Golang快速入门-4(Day7)
  • 要想轻松打开非洲电商市场的大门,你还缺这么一把“钥匙”
  • .Net Core Web Api_笔记04_HTTP资源操作模式Put
  • 美国注册公司是否需要纳税申报?
  • ASP.NET MVC 从入门到放弃(Day8) -C# try catch常见异常和自定义异常 using 介绍
  • [Day03]程序菜鸟自学C++资料结构演算法 – 常见的线性串列其一:阵列Array List
  • 加拿大FBA海运流程和需要的资料有哪些?
  • 外贸人如何应对货物出口延误清关
  • 依赖注入
  • 外贸网络营销需要掌握的几个因素
  • 揭秘速卖通代运营
  • Day23 ( 高级 ) 仙女棒 ( 光迹效果 )
  • Google Play Store报错DF-DFERH-01怎么办
  • Goolge play怎么更改地区下载Apps : 谷歌应用商店更改地区教程
  • Monzo Bank教程:英国银行卡申请教程【教你国内注册申请欧洲银行卡】
  • Windows服务器推荐:便宜好用稳定靠谱的国外Windows服务器推荐
  • 搬瓦工VPS优惠码/ 促销码 、最新BandwagonHost官网促销
  • 虚拟信用卡是什么?虚拟信用卡安全吗?怎么用?怎么申请教程
  • 最好的国外VPS推荐:国外好用便宜性价比高的VPS有哪些
  • 数字人民币是什么?什么是数字人民币
  • Akaxin:开源社交软件/加密聊天/打造你自己的社交产品
  • 最便宜的国外VPS推荐:5美金以下的VPS大全
  • MovoCash虚拟信用卡教程:美国虚拟信用卡申请教程
  • Facebook和instagram推广营销教程
  • 苹果手机Apple美国区账号无信用卡购买教程【Apple ID美国区绑定PayPal教程】
  • Gutenberg 10.4 在自定义程序中引入了块小工具
  • 升级wordpress出错怎么办?wordpress升级502错误解决方法