🧊

[Flutter] cubitのレシピ

2022/04/29に公開

Cubitのレシピ

https://bloclibrary.dev/#/coreconcepts?id=cubit
を読んで、どういう手順で作ればいいのかわからない人向けに手順を紹介

UIとBlocとDataを切り分けることで、さまざまなメリットが得られる
今回は、
UI:ログイン画面
Bloc:Cubit
Data:Repository(httpでサーバーからデータを受け取る)

1.stateを作る

// 抽象クラスで、SiginInStateを作成する
abstract class SignInState {}

// 必要なstate(状態)を考えて、SignInStateを継承させる
// サインイン前の初期状態
class SignInInitialState extends SignInState {}
// サインイン成功した状態
class SignInSuccesState extends SignInState {}
// サインイン失敗状態、エラーメッセージ持たせれたら、いいかも
class SignInErrorState extends SignInState {
  final String message;
  SignInErrorState({required this.message});
}
// サインイン中の状態、データの読み込みに時間かかる場合のグルグルを表示するのに便利
class SignInLoadingState extends SignInState {}

2.cubitを作る

// CubitにSignInStateを持たせたClassを継承するSignInCubitを作成
class SignInCubit extends Cubit<SignInState> {
  // サインイン前の初期状態を設定
  SignInCubit() : super(SignInInitialState());

  // 状態を変えるFutureのFunctionを設定
  // 状態の変化を考えて順に設定していく
  Future<void> signIn(String email, String password) async {
    // 最初は、ローディング状態をUI側に伝える
    emit(SignInLoadingState());
    try {
      // 3.で作成するデータのRepositoryをawaitを使って呼び出す。
      // email,passwordをhttpでサーバーにPostして承認Tokenの文字列を返すようデータ側に伝える
      await SignInRepository().signIn(email, password);

      // 成功した時に成功した状態をUI側に伝える
      emit(SignInSuccesState());
    } catch (error) {
      // SignInRepository().signIn(email, password)の中にある失敗の場合ここに入る
      // 失敗した時の状態をUI側に伝える
      emit(SignInErrorState(message: error.toString()));
      rethrow;
    }
  }
}

3.repositoryを作る

// 何かとClassで定義しているの便利なので、RepositoryのClassを設定
class SignInRepository {
  // httpでデータを取得するFutureを設定
  Future<String> signIn(String email, String password) async {
    // Postの応答を得るもオブジェクトの作成
    final response = await http.post(
      Uri.parse('http://localhost:8080/auth/sign-in'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(<String, String>{
        'email': email,
        'password': password,
      }),
    );

    // SharedPrefsProviderでログイン状態などをアプリに一時的に保存できる。
    final provider = SharedPrefsProvider();

    // http通信のPostが成功した時の条件
    if (response.statusCode == 200) {
      provider.saveAuthToken(response.toString());
      // 3. httpの応答の型はそれぞれ違うので、Stringになるように操作する
      return response.toString();
    } else {
      // 成功以外の場合
      throw Exception('Failed to Login.');
    }
  }
}

4.UIページの変更

長くなるので、重要な部分だけ抜き出して記載
三種の神器よりも有用な、三種のBloc~~~
1.BlocProvider:設定した以下のWidget TreeでCubitが利用可能になるので必須
2.BlocListener:BlocProviderで設定したCubitでemitされたstateを受け取り、状態によってファンクションや値の代入などを行う時に使う
3.BlocBuilder:BlocProviderで設定したCubitでemitされたstateを受け取り、状態によってWidgetを返す時などに使う

...

Widget build(BuildContext context) {
    // BlocProviderを設定して、以下のWidget Treeで使いたいCubitを設定することができる
    return BlocProvider(
      // 使いたいcubitを設定
      create: (context) => SignInCubit(),
      // BlocListenerを設定、BlocListenerには使うcubitとStateを設定する
      // functionや値を代入する時などにListenerを使う、
      // Widgetを使うときはWidgetBuilderを使う
      // stateにはabstract classのStateを設定する
      child: BlocListener<SignInCubit, SignInState>(
        listener: (context, state) {
          // state毎の条件を設定
          // 成功した時のStateの時
          if (state is SignInSuccesState) {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const HomePage(),
              ),
            );
          }
          // 失敗した時のStateの時
          if (state is SignInErrorState) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text("Email or Password is wrong"),
              ),
            );
          }
        },
...
// BlocBuilderを設定してWidgetを返す
child: BlocBuilder<SignInCubit, SignInState>(
      builder: ((context, state) {
      // 読み込み中の時
      if (state is SignInLoadingState) {
         return const SizedBox(
             height: 30,
             width: 30,
             child: CircularProgressIndicator(),
         );
      }
      return const Text('Sign In');
      }),
),
...

5.完成!

Discussion