0%

Flutter 013 - 管理程式碼好幫手 ( Bloc )

前言

Hi,本次教學會用一個簡單的加一減一的範例來教大家 Bloc 這個套件,當你學會以後程式碼會變得非常乾淨,但是內容可能會有一點難希望大家可以看得懂。

完整程式碼

安裝

Bloc 可以讓頁面與邏輯分離變得容易管理,你可以想像他是頁面與邏輯的橋樑,讓程式碼可以快速閱讀、易於測試和可重複使用,這就是為什麼我要介紹他的原因,以下是他的套件,如果你是用vscode的同學建議你可以安裝bloc,這個套件可以為你生成基本架構,equatable則是可以簡化程式碼。

1
2
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的用途。

1
2
3
4
5
6
7
8
9
10
11
12
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+.),就會跳出一個選項來生成我們的函式框架,然後就把我們的功能給實作出來。

1
2
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則是可以讓我們看到事件觸發時傳進去的內容。

1
2
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這個套件,因此我們可以把他縮短成一段讓我們程式碼更有效率,而這幾段程式碼則是幫我們檢查物件是不是相同,不是的話就可以幫我們做覆蓋的動作。

未使用:

1
2
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;
}

使用後:

1
2
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中可以分成三個,第一個是初始化,第二個是判斷使用者事件,第三個是事件觸發後回傳的狀態,在這裡我們會用到StreamStream是用來接收一連串的事件,Stream會監聽目前狀態,若 Stream 有事件,則將告訴監聽器,而其他流程的寫法就以註解的方式來介紹。

1
2
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* {
// 成功加一的話回傳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所有事件。

1
2
3
4
5
6
7
8
9
void main() {
final countRepository = CountRepository();
runApp(
BlocProvider<MyblocBloc>(
create: (context) => MyblocBloc(countRepository: countRepository),
child: MyApp(),
),
);
}

BlocBuilder

BlocBuilder可以處理構建小部件以回應新狀態。

1
2
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 {
// state is Failure
return MyHomePage(count: 0);
}
},
),
);
}
}

使用事件

我們只需要在按鈕onPressed中寫下觸發的事件就可以了。

1
2
3
4
5
6
7
TextButton(
onPressed: () {
BlocProvider.of<MyblocBloc>(context)
.add(IncrementEvent(widget._count));
},
child: Text("Add"),
),

Note:

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