前言
Hi,本次教學會用一個簡單的加一減一的範例來教大家 Bloc 這個套件,當你學會以後程式碼會變得非常乾淨,但是內容可能會有一點難希望大家可以看得懂。
完整程式碼
安裝
Bloc 可以讓頁面與邏輯分離變得容易管理,你可以想像他是頁面與邏輯的橋樑,讓程式碼可以快速閱讀、易於測試和可重複使用,這就是為什麼我要介紹他的原因,以下是他的套件,如果你是用vscode的同學建議你可以安裝bloc,這個套件可以為你生成基本架構,equatable則是可以簡化程式碼。
| 12
 3
 4
 5
 6
 
 | 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的用途。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | abstract class CountRepositoryImp {
 
 
 Future<int> add(int count);
 
 
 
 
 Future<int> dec(int count);
 }
 
 
 | 

將API給接上並實作內容
我們可以在一個Class後面加上implements剛剛創的API,接著對Class的按下快捷鍵(mac:command+.,win:control+.),就會跳出一個選項來生成我們的函式框架,然後就把我們的功能給實作出來。

| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 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則是可以讓我們看到事件觸發時傳進去的內容。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | 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這個套件,因此我們可以把他縮短成一段讓我們程式碼更有效率,而這幾段程式碼則是幫我們檢查物件是不是相同,不是的話就可以幫我們做覆蓋的動作。
未使用:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | 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;
 }
 
 
 | 
使用後:
| 12
 3
 4
 5
 6
 7
 8
 
 | class Success extends MyblocState {final int count;
 
 const Success(this.count);
 
 @override
 List<Object> get props => [count];
 }
 
 | 
最重要的橋樑:Bloc 三部曲
Bloc中可以分成三個,第一個是初始化,第二個是判斷使用者事件,第三個是事件觸發後回傳的狀態,在這裡我們會用到Stream,Stream是用來接收一連串的事件,Stream會監聽目前狀態,若 Stream 有事件,則將告訴監聽器,而其他流程的寫法就以註解的方式來介紹。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 
 | 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* {
 
 try {
 final _count = await _countRepository.add(count);
 yield Success(_count);
 } catch (_) {
 yield Failure();
 }
 }
 
 
 Stream<MyblocState> _mapDecrementToState(int count) async* {
 
 try {
 final _count = await _countRepository.dec(count);
 yield Success(_count);
 } catch (_) {
 yield Failure();
 }
 }
 }
 
 | 
如何使用 Bloc
終於到了最後如何使用,原本想說拆成上下集,但是魚板國王不同意所以只好繼續說下去,不知道大家還有沒有跟上,以下我會介紹Bloc的其中一種用法。
BlocProvider
BlocProvider負責創建Bloc和一個元件,你將可以使用Bloc所有事件。
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | void main() {final countRepository = CountRepository();
 runApp(
 BlocProvider<MyblocBloc>(
 create: (context) => MyblocBloc(countRepository: countRepository),
 child: MyApp(),
 ),
 );
 }
 
 | 
BlocBuilder
BlocBuilder可以處理構建小部件以回應新狀態。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | 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 {
 
 return MyHomePage(count: 0);
 }
 },
 ),
 );
 }
 }
 
 | 
使用事件
我們只需要在按鈕onPressed中寫下觸發的事件就可以了。
| 12
 3
 4
 5
 6
 7
 
 | TextButton(onPressed: () {
 BlocProvider.of<MyblocBloc>(context)
 .add(IncrementEvent(widget._count));
 },
 child: Text("Add"),
 ),
 
 | 

Note:
Bloc還有其他的功能,MultiBlocProvider(創建多個Bloc)、BlocListener(聽取狀態但不能改變元件)、MultiBlocListener(聽取多個狀態但不能改變元件)等等…,就讓大家自己摸索一下囉。