Flutter BLoC 使用者登入 1

2021-05-06 09:00:32

原文

https://medium.com/theotherde...

前言

首先,由於這不是一個基本的教學,我們理所當然地認為這是一個路線的知識,我們也包含了一點點的 validationformoz 包來建立可重用的模型; 這不是本教學的目的,以顯示這將如何工作,您將看到這在下一個教學。對於登入部分,出於教學的目的,我們還使用了 BLoC (Cubit) 的子集,因此您將看到這兩者之間的區別。

程式碼,可以先閱讀程式碼,再看檔案

https://github.com/Alessandro...

參考

正文

開始

在我們開始之前,讓我們在 pubspec.yaml 中新增一些必要的包:

equatable: ^2.0.0
flutter_bloc: ^7.0.0
formz: ^0.3.2

新增 equatable 包只會使您的工作更加容易,但是如果您想手動比較類的範例,只需要重寫 "==" 和 hashCode。

登入狀態

讓我們從一個包含表單狀態和所有欄位狀態的類開始:

class LoginState extends Equatable {
  const LoginState({
    this.email = const Email.pure(),
    this.password = const Password.pure(),
    this.status = FormzStatus.pure,
    this.exceptionError,
  });
  final Email email;
  final Password password;
  final FormzStatus status;
  final String exceptionError;
  @override
  List<Object> get props => [email, password, status, exceptionError];
  LoginState copyWith({
    Email email,
    Password password,
    FormzStatus status,
    String error,
  }) {
    return LoginState(
      email: email ?? this.email,
      password: password ?? this.password,
      status: status ?? this.status,
      exceptionError: error ?? this.exceptionError,
    );
  }
}

現在讓我們建立我們的 LoginCubit,它將負責執行邏輯,例如通過 emit 獲取電子郵件和輸出新狀態:

class LoginCubit extends Cubit<LoginState> {
  LoginCubit() : super(const LoginState());
  void emailChanged(String value) {
    final email = Email.dirty(value);
    emit(state.copyWith(
      email: email,
      status: Formz.validate([
        email,
        state.password
      ]),
    ));
  }
  void passwordChanged(String value) {
    final password = Password.dirty(value);
    emit(state.copyWith(
      password: password,
      status: Formz.validate([
        state.email,
        password
      ]),
    ));
  }
  Future<void> logInWithCredentials() async {
    if (!state.status.isValidated) return;
    emit(state.copyWith(status: FormzStatus.submissionInProgress));
    try {
      await Future.delayed(Duration(milliseconds: 500));
      emit(state.copyWith(status: FormzStatus.submissionSuccess));
    } on Exception catch (e) {
      emit(state.copyWith(status: FormzStatus.submissionFailure, error: e.toString()));
    }
  }
}

但是我們如何將腕尺與我們的使用者介面連線起來呢?下面是對 BlocProvider 的解救,這是一個小部件,它使用: BlocProvider.of<logincubit>(context) 為其子部件提供一個區塊

BlocProvider(
  create: (_) => LoginCubit(),
  child: LoginForm(),
),

登入表格

既然現在似乎都在他自己的地方,是時候解決我們的最後一塊 puzzle,整個使用者介面

class LoginForm extends StatelessWidget {
  const LoginForm({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return BlocConsumer<LoginCubit, LoginState>(
        listener: (context, state) {
          if (state.status.isSubmissionFailure) {
            print('submission failure');
          } else if (state.status.isSubmissionSuccess) {
            print('success');
          }
        },
        builder: (context, state) => Stack(
          children: [
            Positioned.fill(
              child: SingleChildScrollView(
                padding: const EdgeInsets.fromLTRB(38.0, 0, 38.0, 8.0),
                child: Container(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: [
                      _WelcomeText(),
                      _EmailInputField(),
                      _PasswordInputField(),
                      _LoginButton(),
                      _SignUpButton(),
                    ],
                  ),
                ),
              ),
            ),
            state.status.isSubmissionInProgress
                ? Positioned(
              child: Align(
                alignment: Alignment.center,
                child: CircularProgressIndicator(),
              ),
            ) : Container(),
          ],
        )
    );
  }
}

為了對 Cubit 發出的新狀態做出反應,我們需要將我們的表單包裹在一個 BlocConsumer 中,現在我們將暴露一個監聽者和一個建造者。

  • Listener

這裡我們將監聽狀態更改,例如,在響應 API 呼叫時顯示錯誤或執行導航。

  • Builder

在這裡,我們將顯示 ui 反應狀態的變化,我們的 Cubit

使用者介面

我們的使用者介面由一個列和 5 個子元素組成,但是我們只展示 2 個簡短的小部件:

class _EmailInputField extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<LoginCubit, LoginState>(
      buildWhen: (previous, current) => previous.email != current.email,
      builder: (context, state) {
        return AuthTextField(
          hint: 'Email',
          key: const Key('loginForm_emailInput_textField'),
          keyboardType: TextInputType.emailAddress,
          error: state.email.error.name,
          onChanged: (email) => context
              .read<LoginCubit>()
              .emailChanged(email),
        );
      },
    );
  }
}
class _LoginButton extends StatelessWidget {
  const _LoginButton({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<LoginCubit, LoginState>(
      buildWhen: (previous, current) => previous.status != current.status,
      builder: (context, state) {
        return CupertinoButton(
            child: Text('Login'),
            onPressed: state.status.isValidated
                ? () => context.read<LoginCubit>().logInWithCredentials()
                : null
        );
      },
    );
  }
}

這兩個小部件都包裝在一個 BlocBuilder 中,只有當肘位為它們各自的評估屬性發出新的狀態時,BlocBuilder 才負責重新構建這些小部件,因此,例如,如果使用者沒有在 email 欄位中鍵入任何內容,EmailInputField 將永遠不會被重新構建。

相反,如果所有欄位都經過驗證,按鈕將呼叫 logInWithCredentials() 函數,該函數將根據 API 響應發出一個新狀態(失敗或成功)。

老鐵記得 點贊、轉發 ,我將更有動力呈現 Flutter 好文~~~~


© 貓哥

https://ducafecat.tech/

https://github.com/ducafecat

往期

開源

GetX Quick Start

https://github.com/ducafecat/...

新聞使用者端

https://github.com/ducafecat/...

strapi 手冊譯文

https://getstrapi.cn

微信討論群 ducafecat

系列集合

譯文

https://ducafecat.tech/catego...

Dart 程式語言基礎

https://space.bilibili.com/40...

Flutter 零基礎入門

https://space.bilibili.com/40...

Flutter 實戰從零開始 新聞使用者端

https://space.bilibili.com/40...

Flutter 元件開發

https://space.bilibili.com/40...

Flutter Bloc

https://space.bilibili.com/40...

Flutter Getx4

https://space.bilibili.com/40...

Docker Yapi

https://space.bilibili.com/40...