[Day13] Flutter - 管理程序码好帮手 ( Bloc )

前言

Hi我是鱼板伯爵,本次教学会用一个简单的加一减一的范例来教大家 Bloc 这个套件,当你学会以後程序码会变得非常乾净,但是内容可能会有一点难希望大家可以看得懂。

完整程序码

安装

Bloc 可以让页面与逻辑分离变得容易管理,你可以想像他是页面与逻辑的桥梁,让程序码可以快速阅读、易於测试和可重复使用,这就是为什麽我要介绍他的原因,以下是他的套件,如果你是用vscode的同学建议你可以安装bloc,这个套件可以为你生成基本架构,equatable则是可以简化程序码。

dependencies:
  flutter:
    sdk: flutter
  bloc: ^7.0.0
  flutter_bloc: ^7.0.1
  equatable: ^2.0.3

Bloc 的流程

为让大家更容易理解,可以先把这个套件当成一台贩卖机,你对贩卖机投了10元(Event),卖贩机检查你的金额(Bloc),确认金额10元後萤幕显示10元(State),10元的饮料按钮就会亮灯。

开始建立 Bloc

如果有安装 vscode 的 bloc 套件,只需要点击右键就可以创建一个 Bloc 的基本架构,分别为 Event、State 和Bloc。

先从 Model 开始做起

我们的功能主要就只有两个,第一个是加一,第二个是减一。先创建一个结构来定义我们的函式,我们可以在他上面写一些说明文件,如果滑鼠移到上面则会说明这个API的用途。

abstract class CountRepositoryImp {
  /// Count model
  ///
  /// Increment one
  Future<int> add(int count);

  /// Count model
  ///
  /// Decrement one
  Future<int> dec(int count);
}

将API给接上并实作内容

我们可以在一个Class後面加上implements刚刚创的API,接着对Class的按下快捷键(mac:command+.,win:control+.),就会跳出一个选项来生成我们的函式框架,然後就把我们的功能给实作出来。

class CountRepository implements CountRepositoryImp {
  @override
  Future<int> add(count) async {
    return ++count;
  }

  @override
  Future<int> dec(int count) async {
    return --count;
  }
}

使用者触发事件 : Event

使用者处发的事件有两个加一和减一,先在这两个Event里面宣告count,让我们的数字作加减,当count传进触发的是加一事件,那他就必须把我的count加一後传回来给我,toString则是可以让我们看到事件触发时传进去的内容。

part of 'mybloc_bloc.dart';

abstract class MyblocEvent extends Equatable {
  const MyblocEvent();

  @override
  List<Object> get props => [];
}

class IncrementEvent extends MyblocEvent {
  final int count;

  const IncrementEvent(this.count);

  @override
  String toString() => 'IncrementEvent(count: $count)';
}

class DecrementEvent extends MyblocEvent {
  final int count;

  const DecrementEvent(this.count);

  @override
  String toString() => 'DecrementEvent(count: $count)';
}

回传状态:State

我的状态分别切成MyblocInitial(初始)、Success(成功)和Failure(失败)三个状态,接着在Success里宣告一个count来将处理完的count传进来,然後对状态名称案下快捷键,选择equatable就会生成@override的三段程序码,不过我们有安装equatable这个套件,因此我们可以把他缩短成一段让我们程序码更有效率,而这几段程序码则是帮我们检查物件是不是相同,不是的话就可以帮我们做覆盖的动作。

未使用:

class Success extends MyblocState {
  final int count;

  const Success(this.count);

  @override
  String toString() => 'Increment success(count: $count)';

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;

    return other is AddSuccess && other.count == count;
  }

  @override
  int get hashCode => count.hashCode;
}

使用後:

class Success extends MyblocState {
  final int count;

  const Success(this.count);

  @override
  List<Object> get props => [count];
}

最重要的桥梁:Bloc 三部曲

Bloc中可以分成三个,第一个是初始化,第二个是判断使用者事件,第三个是事件触发後回传的状态,在这里我们会用到StreamStream是用来接收一连串的事件,Stream会监听目前状态,若 Stream 有事件,则将告诉监听器,而其他流程的写法就以注解的方式来介绍。

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:day13/count_repository.dart';
import 'package:equatable/equatable.dart';

part 'mybloc_event.dart';
part 'mybloc_state.dart';

class MyblocBloc extends Bloc<MyblocEvent, MyblocState> {
  CountRepository _countRepository;

  // 初始化
  MyblocBloc({required CountRepository countRepository})
      : _countRepository = countRepository,
        super(MyblocInitial());

  // 触发事件
  @override
  Stream<MyblocState> mapEventToState(
    MyblocEvent event,
  ) async* {
    // 当使用者处发加一的事件时
    if (event is IncrementEvent) {
      // 调用加一的状态
      yield* _mapIncrementToState(event.count);
    }
    // 当使用者处发减一的事件时
    if (event is DecrementEvent) {
      // 调用减一的状态
      yield* _mapDecrementToState(event.count);
    }
  }

  // 状态
  Stream<MyblocState> _mapIncrementToState(int count) async* {
    // 成功加一的话回传Success否则Failure
    try {
      final _count = await _countRepository.add(count);
      yield Success(_count);
    } catch (_) {
      yield Failure();
    }
  }

  // 状态
  Stream<MyblocState> _mapDecrementToState(int count) async* {
    // 成功减一的话回传Success否则Failure
    try {
      final _count = await _countRepository.dec(count);
      yield Success(_count);
    } catch (_) {
      yield Failure();
    }
  }
}

如何使用 Bloc

终於到了最後如何使用,原本想说拆成上下集,但是鱼板国王不同意所以只好继续说下去,不知道大家还有没有跟上,以下我会介绍Bloc的其中一种用法。

BlocProvider

BlocProvider负责创建Bloc和一个元件,你将可以使用Bloc所有事件。

void main() {
  final countRepository = CountRepository();
  runApp(
    BlocProvider<MyblocBloc>(
      create: (context) => MyblocBloc(countRepository: countRepository),
      child: MyApp(),
    ),
  );
}

BlocBuilder

BlocBuilder可以处理构建小部件以回应新状态。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocBuilder<MyblocBloc, MyblocState>(
        builder: (context, state) {
          log("$state");
          if (state is Success) {
            return MyHomePage(count: state.count);
          } else {
            // state is Failure
            return MyHomePage(count: 0);
          }
        },
      ),
    );
  }
}

使用事件

我们只需要在按钮onPressed中写下触发的事件就可以了。

  TextButton(
    onPressed: () {
      BlocProvider.of<MyblocBloc>(context)
          .add(IncrementEvent(widget._count));
    },
    child: Text("Add"),
  ),

Note:

Bloc还有其他的功能,MultiBlocProvider(创建多个Bloc)、BlocListener(听取状态但不能改变元件)、MultiBlocListener(听取多个状态但不能改变元件)等等...,就让大家自己摸索一下罗。


<<:  [13th-铁人赛]Day 7:Modern CSS 超详细新手攻略 - 伪类 Pseudo Classes

>>:  33岁转职者的前端笔记-DAY 13 图片格式及影音格式

Day_07 : 让 Vite 来开启你的Vue 之 Vite 核心 esbuild

Hi Dai Gei Ho~ 我是Winnie~ 今天终於来到我的第七天,按照七天养成一个好习惯的说...

jQuery -jsPDF - html汇出PDF

step 1.import script.js 2.add export_content <d...

Material UI in React [ Day 11] Date & Time picker 日期时间输入

Date / Time pickers 可以透过官方文件知道有两种做法,一种是利用原生 input ...

鼠年全马铁人挑战 WEEK 34:负载性能测试 - Gatling (上)

           Photo on gatling.io 前言 前几周小弟介绍了一款负载性能的...

解决 "No manual entry for gcc" 的记录

问题: $ man gcc No manual entry for gcc 看到这个方法,但是失败了...