前言
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 { Future<int> add(int count);
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中可以分成三個,第一個是初始化,第二個是判斷使用者事件,第三個是事件觸發後回傳的狀態,在這裡我們會用到Stream
,Stream
是用來接收一連串的事件,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* { 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所有事件。
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 { 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(聽取多個狀態但不能改變元件)等等…,就讓大家自己摸索一下囉。