You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从GetX迁移:多功能Flutter认证页面Bloc/Cubit使用方案咨询

迁移GetX认证页面到Bloc/Cubit:方案与实现

核心结论

用单个Cubit统一管理所有状态即可。这些字段(认证类型、密码可见性、性别、出生日期等)都是认证页面的局部状态,彼此存在关联(比如登录/注册切换会影响表单字段显示,确认密码依赖密码值),拆分多个Cubit会增加状态同步的复杂度,反而降低开发效率。


一、具体实现步骤

1. 定义不可变的AuthState

包含所有可变状态,使用equatable确保状态对比的正确性:

import 'package:equatable/equatable.dart';

enum AuthType { login, signUp }

class AuthState extends Equatable {
  final AuthType authType;
  final String name;
  final String email;
  final String phoneNumber;
  final String password;
  final String confirmPassword;
  final bool showPassword;
  final bool showConfirmPassword;
  final DateTime? dateOfBirth;
  final int? selectedGender;
  final String selectedCountryFlag;
  final String countryCode;
  final String phoneCode;
  // 可选:添加加载状态处理提交逻辑
  // final bool isLoading;

  const AuthState({
    this.authType = AuthType.login,
    this.name = '',
    this.email = '',
    this.phoneNumber = '',
    this.password = '',
    this.confirmPassword = '',
    this.showPassword = false,
    this.showConfirmPassword = false,
    this.dateOfBirth,
    this.selectedGender,
    this.selectedCountryFlag = '',
    this.countryCode = '',
    this.phoneCode = '',
    // this.isLoading = false,
  });

  AuthState copyWith({
    AuthType? authType,
    String? name,
    String? email,
    String? phoneNumber,
    String? password,
    String? confirmPassword,
    bool? showPassword,
    bool? showConfirmPassword,
    DateTime? dateOfBirth,
    int? selectedGender,
    String? selectedCountryFlag,
    String? countryCode,
    String? phoneCode,
    // bool? isLoading,
  }) {
    return AuthState(
      authType: authType ?? this.authType,
      name: name ?? this.name,
      email: email ?? this.email,
      phoneNumber: phoneNumber ?? this.phoneNumber,
      password: password ?? this.password,
      confirmPassword: confirmPassword ?? this.confirmPassword,
      showPassword: showPassword ?? this.showPassword,
      showConfirmPassword: showConfirmPassword ?? this.showConfirmPassword,
      dateOfBirth: dateOfBirth ?? this.dateOfBirth,
      selectedGender: selectedGender ?? this.selectedGender,
      selectedCountryFlag: selectedCountryFlag ?? this.selectedCountryFlag,
      countryCode: countryCode ?? this.countryCode,
      phoneCode: phoneCode ?? this.phoneCode,
      // isLoading: isLoading ?? this.isLoading,
    );
  }

  @override
  List<Object?> get props => [
        authType,
        name,
        email,
        phoneNumber,
        password,
        confirmPassword,
        showPassword,
        showConfirmPassword,
        dateOfBirth,
        selectedGender,
        selectedCountryFlag,
        countryCode,
        phoneCode,
        // isLoading,
      ];
}

2. 实现AuthCubit处理所有状态逻辑

集中管理表单的所有操作,包括状态更新、表单提交、第三方登录等:

import 'package:bloc/bloc.dart';

class AuthCubit extends Cubit<AuthState> {
  AuthCubit() : super(const AuthState());

  // 切换登录/注册模式
  void toggleAuthType() {
    emit(state.copyWith(
      authType: state.authType == AuthType.login ? AuthType.signUp : AuthType.login,
      // 切换时重置注册专属字段
      name: '',
      phoneNumber: '',
      dateOfBirth: null,
      selectedGender: null,
    ));
  }

  // 更新表单输入值
  void updateName(String value) => emit(state.copyWith(name: value));
  void updateEmail(String value) => emit(state.copyWith(email: value));
  void updatePhoneNumber(String value) => emit(state.copyWith(phoneNumber: value));
  void updatePassword(String value) => emit(state.copyWith(password: value));
  void updateConfirmPassword(String value) => emit(state.copyWith(confirmPassword: value));

  // 切换密码可见性
  void toggleShowPassword() => emit(state.copyWith(showPassword: !state.showPassword));
  void toggleShowConfirmPassword() => emit(state.copyWith(showConfirmPassword: !state.showConfirmPassword));

  // 选择出生日期
  void selectDateOfBirth(DateTime date) => emit(state.copyWith(dateOfBirth: date));

  // 选择性别
  void selectGender(int gender) => emit(state.copyWith(selectedGender: gender));

  // 选择国家
  void selectCountry({
    required String countryCode,
    required String countryFlag,
    required String phoneCode,
  }) {
    emit(state.copyWith(
      countryCode: countryCode,
      selectedCountryFlag: countryFlag,
      phoneCode: phoneCode,
    ));
  }

  // 提交表单
  Future<void> submitForm() async {
    // emit(state.copyWith(isLoading: true));
    try {
      if (state.authType == AuthType.login) {
        // 执行登录API请求
      } else {
        // 执行注册API请求
      }
    } catch (e) {
      // 处理错误,比如弹出提示
    } finally {
      // emit(state.copyWith(isLoading: false));
    }
  }

  // Google登录
  Future<void> signInWithGoogle() async {
    // 执行Google登录逻辑
  }
}

3. 改造AuthView页面

替换GetX依赖为Bloc/Cubit的BlocBuilder,实现状态监听与UI更新:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class AuthView extends StatelessWidget {
  const AuthView({super.key});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final customTheme = // 你的自定义主题实例
    final localizations = // 你的本地化实例
    return UpgradeDialog(
      child: SafeArea(
        child: Scaffold(
          body: BlocBuilder<AuthCubit, AuthState>(
            builder: (context, state) {
              return Form(
                child: CustomScrollView(
                  physics: const ClampingScrollPhysics(),
                  slivers: [
                    SliverPadding(
                      padding: const EdgeInsets.symmetric(horizontal: 16.0),
                      sliver: SliverList.list(
                        children: [
                          // 页面头部Logo、标题等固定内容...
                          SizedBox(
                            height: state.authType == AuthType.login ? 64.0 : 32.0,
                          ),
                          // 表单内容开始
                          AutofillGroup(
                            child: Column(
                              children: [
                                if (state.authType == AuthType.signUp) ...[
                                  CustomTextField(
                                    onChanged: context.read<AuthCubit>().updateName,
                                    initialValue: state.name,
                                    label: localizations.fullName,
                                    // 其他属性保持不变...
                                  ),
                                ],
                                CustomTextField(
                                  onChanged: context.read<AuthCubit>().updateEmail,
                                  initialValue: state.email,
                                  label: localizations.email,
                                  // 其他属性保持不变...
                                ),
                                if (state.authType == AuthType.signUp) ...[
                                  PhoneTextField(
                                    onChanged: context.read<AuthCubit>().updatePhoneNumber,
                                    initialValue: state.phoneNumber,
                                    selectedCountryFlag: state.selectedCountryFlag,
                                    onSelect: (Country country) {
                                      context.read<AuthCubit>().selectCountry(
                                        countryCode: country.countryCode,
                                        countryFlag: country.flagEmoji,
                                        phoneCode: country.phoneCode,
                                      );
                                    },
                                    // 其他属性保持不变...
                                  ),
                                ],
                                CustomTextField(
                                  onChanged: context.read<AuthCubit>().updatePassword,
                                  initialValue: state.password,
                                  obscureText: !state.showPassword,
                                  suffixIcon: IconButton(
                                    onPressed: context.read<AuthCubit>().toggleShowPassword,
                                    icon: Icon(
                                      state.showPassword
                                          ? FontAwesomeIcons.eye
                                          : FontAwesomeIcons.eyeSlash,
                                      // 其他属性保持不变...
                                    ),
                                  ),
                                  // 其他属性保持不变...
                                ),
                                if (state.authType == AuthType.signUp) ...[
                                  CustomTextField(
                                    onChanged: context.read<AuthCubit>().updateConfirmPassword,
                                    initialValue: state.confirmPassword,
                                    obscureText: !state.showConfirmPassword,
                                    suffixIcon: IconButton(
                                      onPressed: context.read<AuthCubit>().toggleShowConfirmPassword,
                                      icon: Icon(
                                        state.showConfirmPassword
                                            ? FontAwesomeIcons.eye
                                            : FontAwesomeIcons.eyeSlash,
                                        // 其他属性保持不变...
                                      ),
                                    ),
                                    validator: (value) {
                                      return ValidatorsManager().validateConfirmPassword(value, state.password);
                                    },
                                    // 其他属性保持不变...
                                  ),
                                  DateFormPicker(
                                    initialDate: state.dateOfBirth,
                                    onSelected: (date) => context.read<AuthCubit>().selectDateOfBirth(date),
                                    // 其他属性保持不变...
                                  ),
                                  GenderFormPicker(
                                    selectedGender: state.selectedGender,
                                    onSelect: (value) {
                                      if (value != null) context.read<AuthCubit>().selectGender(value);
                                    },
                                    validator: (_) => ValidatorsManager.validateGender(state.selectedGender),
                                    // 其他属性保持不变...
                                  ),
                                ],
                                MainButton(
                                  title: state.authType == AuthType.login ? localizations.login : localizations.createAccount,
                                  onPressed: context.read<AuthCubit>().submitForm,
                                  // 可选:根据isLoading禁用按钮
                                  // isDisabled: state.isLoading,
                                ),
                                // 第三方登录、切换登录/注册按钮等内容...
                                InkWell(
                                  onTap: context.read<AuthCubit>().toggleAuthType,
                                  child: ScaleText(
                                    state.authType == AuthType.login ? localizations.createAccount : localizations.login,
                                    // 其他属性保持不变...
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ],
                      ),
                    ),
                    // 底部版权信息...
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

4. 注入AuthCubit

在页面入口用BlocProvider提供Cubit实例:

BlocProvider(
  create: (context) => AuthCubit(),
  child: const AuthView(),
)

二、补充说明

  1. 状态扩展:如果后续需要添加加载、错误提示等状态,直接在AuthState中新增字段即可,无需拆分Cubit。
  2. 表单验证:本地验证可以结合Cubit状态(比如确认密码直接取state.password),提交时先做本地校验再发起请求。
  3. 导航替换:用Flutter原生Navigator代替GetX路由,保持Bloc生态的一致性。

内容的提问来源于stack exchange,提问作者Osama Assaf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:30:55