【第九天 - 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));

<<:  NNI安装後的验证02

>>:  【Day 8】Google Apps Script - 依文件实作 Demo 用的 API

AutoCAD ActiveX #1 安装环境

VBA IDE Command:VBAIDE 需下载对应年份 VBAIDE汇入参考 执行程序 环境设...

Day2 - 安装Android Studio

昨天我们下载完Android Studio了 那今天就来安装Android Studio八 安装An...

【在厨房想30天的演算法】Day 14 演算法 : 排序 sort I 气泡、选择、插入

Aloha~!又是我少女人妻 Uerica!今天终於可以进入到演算法的章节啦 (欢呼) ~ 之前因为...

全端入门Day20_前端程序撰写之多一点的JavaScript

昨天介绍了ID跟class,今天要把button给弄好 JavaScript再次入门 JavaScr...

[Day 03]取得Nonce与HashID以产出Sign - [C#]丰收款API必备前置作业(二)

首先来个永丰官方的文件图片作开场吧! (图一:由商户->永丰正向发动的所需参数) 而我们今天要...