阅读 332

Flutter BLoC 用户登录 1

原文

medium.com/theotherdev…

前言

首先,由于这不是一个基本的教程,我们理所当然地认为这是一个路线的知识,我们也包含了一点点的 validationformoz 包来创建可重用的模型; 这不是本教程的目的,以显示这将如何工作,您将看到这在下一个教程。对于登录部分,出于教程的目的,我们还使用了 BLoC (Cubit) 的子集,因此您将看到这两者之间的区别。

代码,可以先阅读代码,再看文档

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 好文~~~~


© 猫哥

ducafecat.tech/

github.com/ducafecat

往期

开源

GetX Quick Start

github.com/ducafecat/g…

新闻客户端

github.com/ducafecat/f…

strapi 手册译文

getstrapi.cn

微信讨论群 ducafecat

系列集合

译文

ducafecat.tech/categories/…

Dart 编程语言基础

space.bilibili.com/404904528/c…

Flutter 零基础入门

space.bilibili.com/404904528/c…

Flutter 实战从零开始 新闻客户端

space.bilibili.com/404904528/c…

Flutter 组件开发

space.bilibili.com/404904528/c…

Flutter Bloc

space.bilibili.com/404904528/c…

Flutter Getx4

space.bilibili.com/404904528/c…

Docker Yapi

space.bilibili.com/404904528/c…

文章分类
前端
文章标签