什麼是Bloc Design Pattern
Bloc 全名是「Business Logic Component」,用來分離UI層和商業邏輯層,有助於程式碼的維護、重複利用以及測試。
Bloc 的流程

Bloc 層會接收使用者的行為 (例如點擊Button),根據不同的事件(Event)Bloc會去呼叫相應的函式(可能是去資料庫抓資料或使用 api )。 之後根據結果回傳相應的狀態 (State) 給UI層,UI則根據狀態做變化。
使用Flutter Bloc 套件
Flutter Bloc 套件是由Felix Angelov所開發的套件,能夠幫助開發人員實作 Bloc pattern。 他將 Bloc 包裝成更容易理解和維護的框架,而且把許多細節都幫我們做好了(例如關閉Stream、Log等等)。
Bloc Event
abstract class LoginEvent extends Equatable {
LoginEvent([List props = const []]) : super(props);
}
class EmailChanged extends LoginEvent {
final String email;
EmailChanged({@required this.email}) : super([email]);
@override
String toString() => 'EmailChanged { email :$email }';
}
Bloc State
class LoginState {
bool isEmailValid;
final bool isPasswordValid;
LoginState({
@required this.isEmailValid,
@required this.isPasswordValid,
});
factory LoginState.empty() {
return LoginState(
isEmailValid: true,
isPasswordValid: true,
);
}
}
Bloc
定義 Bloc 物件需先繼承套件提供的 Bloc class,並給予定義好的Event物件和State物件,和定義 Bloc 初始的State。
另外一定要實作的是mapEventToState,將接收到的Event用對應的商業邏輯(Business Logic)做處理,最後回傳包裝成Stream的State
class LoginBloc extends Bloc<LoginEvent, LoginState> {
@override
LoginState get initialState => LoginState.init();
@override
Stream<LoginState> mapEventToState(LoginEvent event) async* {
// 依照接收到的Event執行對應的方法
if (event is LoginWithCredentialsPressed) {
yield* _mapLoginWithCredentialsPressedToState(
email: event.email,
password: event.password,
);
}
}
Stream<LoginState> _mapLoginWithCredentialsPressedToState({
String email,
String password,
}) async* {
// 使用帳號和密碼進行登入,成功就回傳sucess的State
// 失敗則回傳Failure的State
try {
await _userRepository.signInWithCredentials(email, password);
yield LoginState.success();
} catch (_) {
yield LoginState.failure();
}
}
}
Dispatch
那麼該如何觸發Event呢?
Bloc內提供dispatch 方法可以用Event作為參數,它會觸發mapEventToState,接著就是執行Event與State的轉換。
// 示意用程式碼
void main() {
LoginBloc bloc = LoginBloc();
// 略...
RaisedButton(
onPressed: (){
bloc.dispatch(LoginWithGooglePressed);
},
child: Text('Google Login'),
)
}
BlocProvider
在 Bloc package中,Widget 本身並不會直接擁有、或是直接參照 Bloc,而是透過 BlocProvider 將 Bloc、
以及用到這個 Bloc 的 Widget 綁起來,Widget 要在 Widget Tree 當中往上找到 BlocProvider 之後,再跟 BlocProvider 詢問 Bloc。
建立 BlocProvider 的方式像這樣。我們有一個上層的 Widget,裡頭包含了一個與登入相關的 AuthenticationBloc,
而我們 App 中的主要畫面都在 PageWidget 裡頭的話:
var _bloc = AuthenticationBloc();
Widget build(BuildContext context) {
return BlocProvider(
bloc: _bloc,
child: PageWidget()),
);
}
在 PageWidget,以及 PageWidget 以下任何一層的所有 children,都可以往上、在 build context 中找到 AuthenticationBloc:
final authenticationBloc = BlocProvider.of<Bloc<AuthenticationEvent, AuthenticationState>>(context);
BlocBuilder
我們會希望 Bloc 在狀態更新之後,自動更新相關的 UI,而不是我們自己再去呼叫 setState(),
而 Bloc package 提供了 BlocBuilder,只要是這個 Bloc 發生變動,就會執行我們所指定的 WidgetBuilder。
final authenticationBloc = BlocProvider.of<Bloc<AuthenticationEvent, AuthenticationState>>(context);
return BlocBuilder<AuthenticationEvent, AuthenticationState>(
bloc: authenticationBloc,
builder: (context, state) {
if (state is AuthenticatedState) {
return Text('已登入 ${state.email}');
} else {
return Text('尚未登入');
}
});