iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 11
2
Mobile Development

Flutter---Google推出的跨平台框架,Android、iOS一起搞定系列 第 11

【Flutter基礎概念與實作】 Day11–Flutter Bloc 套件介紹 (1) Events、States和Transitions

昨天提到BLoC Design Pattern,那麼今天來介紹這次專案使用的Bloc套件吧。

Bloc

a predictable state management library for Dart.
Simple & Lightweight
Highly Testable
For Dart, Flutter, and AngularDart

Flutter BLoc套件是由Felix Angelov所開發的套件,能夠幫助開發人員實作BLoC pattern。他將BLoC包裝成更容易理解和維護的框架,而且把許多細節都幫我們做好了(例如關閉Stream、Log等等)。

Bloc 與 Flutter_Bloc

Felix Angelov將Bloc分成兩個套件:

  • Bloc:bloc的核心套件,State、Event、Stream都包含在裡面。
  • Flutter_Bloc:配合使用Bloc的Flutter widget,例如BlocBuilderBlocProviderBlocListener

今天先介紹Bloc套件內的名詞和定義Bloc物件。

Events、States和Transitions

  • Events 事件:
    Events指的是使用者觸發的事件,以登入為例,當使用者點下登入的按鈕後,便觸發登入帳號的事件。
    也就是下圖的Input,會將事件傳到Bloc中去做處理。

    定義Events可以簡單的用enum例如:
enum LoginEvent{LoginWithGooglePressed, LoginWithCredentialsPressed}
// LoginWithGooglePressed = 0;
// LoginWithCredentialsPressed = 1;

或者使用class來定義,使用class的好處是可以另外傳遞資訊到Bloc中,例如需要傳遞信箱和密碼的時候就要使用class來定義事件。

class LoginWithGooglePressed extends LoginEvent {
  @override
  String toString() => 'LoginWithGooglePressed';
}

class LoginWithCredentialsPressed extends LoginEvent {
  final String email;
  final String password;

  LoginWithCredentialsPressed({@required this.email, @required this.password})
      : super([email, password]);

  @override
  String toString() {
    return 'LoginWithCredentialsPressed { email: $email, password: $password }';
  }
}
  • States 狀態:
    剛剛的Events是Bloc的Input,而這裡的States是Bloc的Output,UI會依照States的改變而有相對應的變化。
    同樣以登入例子來說,Bloc處理完登入的工作後可能會回傳「Success」或是「Failure」,UI會依照回傳值轉換成「登入成功介面」或是顯示「登入錯誤的警示」
factory LoginState.init() {
    return LoginState(
      isSuccess: false,
      isFailure: false,
    );
  }
  
factory LoginState.failure() {
    return LoginState(
      isSuccess: false,
      isFailure: true,
    );
  }

  factory LoginState.success() {
    return LoginState(
      isSuccess: true,
      isFailure: false,
    );
  }
  • Transitions 過渡:
    Transitions沒有一個確切的中文翻譯,但我覺得比較貼近的翻譯是過渡或是變遷。它的意思是從一個State轉變到另一個State的過程。
    Bloc套件很貼心的會將狀態變化記錄下來,可以簡單得用來追蹤使用者的操作跟Debug。

例如當登入事件完成後可以在log看到以下資訊。(需要override onTransition或使用BlocDelegate)

Transition { 
    currentState: LoginState {
       isSuccess: false,
       isFailure: false,
     }, 
     event: LoginWithGooglePressed, 
     nextState: LoginState {
       isSuccess: true,
       isFailure: false,
     } 
}

Blocs

定義Bloc物件需先繼承套件提供的Bloc class,並給予定義好的Event物件和State物件。

import 'package:bloc/bloc.dart';

// LoginBloc會接收LoginEvent作為輸入,並輸出LoginState。

class LoginBloc extends Bloc<LoginEvent, LoginState> {
}

還需要給予Bloc初始的State,在還沒接收到任何Event前就使用初始的State,不然UI會不知道該顯示什麼畫面才好。

// 這裡的LoginState.init()和上面State程式碼的一樣。

  @override
  LoginState get initialState => LoginState.init();

另外一定要實作的是mapEventToState,將接收到的Event用對應的商業邏輯(Business Logic)做處理,最後回傳包裝成Stream的State

@override
  Stream<LoginState> mapEventToState(LoginEvent event) async* {
  // 依照接收到的Event執行對應的方法
    if (event is LoginWithGooglePressed) {
      yield* _mapLoginWithGooglePressedToState();
    } else if (event is LoginWithCredentialsPressed) {
      yield* _mapLoginWithCredentialsPressedToState(
        email: event.email,
        password: event.password,
      );
    }
  }

  Stream<LoginState> _mapLoginWithGooglePressedToState() async* {
  // 使用Google帳號進行登入,成功就回傳sucess的State
  // 失敗則回傳Failure的State
    try {
      await _userRepository.signInWithGoogle();
      yield LoginState.success();
    } catch (_) {
      yield LoginState.failure();
    }
  }

  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();
    }
  }

如此一來就定義好LoginBloc所需的所有東西了,當使用者做了某些行為會觸發Bloc的商業邏輯,之後Bloc會更新State,UI就依照State來更新介面。

Dispatch

那麼該如何觸發Event呢?
Bloc內提供dispatch 方法可以用Event作為參數,它會觸發mapEventToState,接著就是執行Event與State的轉換。

// 示意用程式碼
void main() {
    LoginBloc bloc = LoginBloc();
    // 略...
    RaisedButton(
      onPressed: (){
          bloc.dispatch(LoginWithGooglePressed);
      },
      child: Text('Google Login'),
    )
}

BlocDelegate

在Transitions章節有提到,如果要log或顯示Transition就需要override onTransition(另外還有onErroronEvent分別能處理Exception和Event資訊),但一個專案中可能會有多個Bloc Class,如果每個都要override實在很麻煩。

這時候就可以使用BlocDelegate,它可以一次設定所有的Bloc class。
定義的方式如下,可以依照需求添加功能,例如例外處理:

class SimpleBlocDelegate extends BlocDelegate {
  @override
  void onEvent(Bloc bloc, Object event) {
    super.onEvent(bloc, event);
    print(event);
  }

  @override
  void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print(transition);
  }

  @override
  void onError(Bloc bloc, Object error, StackTrace stacktrace) {
    super.onError(bloc, error, stacktrace);
    print('$error, $stacktrace');
  }
}

使用起來也很簡單只需要在main()裡加上一行程式碼就可以了,剩下就交給Delegate處理:

main() {
  BlocSupervisor.delegate = SimpleBlocDelegate();
  runApp(App());
}

今日總結

今天介紹了「bloc」套件提供的物件以及使用方式,第一次接觸可能會覺得很複雜又得要定義StateEvent很麻煩。不過就是因為這個套件將BLoC的Input和Output定義得很清楚,後續要做測試和追蹤會變得非常容易。
另外有方便的擴充套件只要輸入bloc名稱就可以產生基本的bloc模板,所以需要打的程式碼其實並不多。
明天來介紹「flutter_bloc」套件提供的各種flutter widget的用法以及擴充套件該如何安裝跟使用。


上一篇
【Flutter基礎概念與實作】 Day10–Firebase與Bloc Design Pattern
下一篇
【Flutter基礎概念與實作】 Day12–Flutter Bloc 套件介紹 (2) BlocBuilder、BlocProvider和BlocListener
系列文
Flutter---Google推出的跨平台框架,Android、iOS一起搞定30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言