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

如何在Hydrated Bloc中持久化复杂的登录状态对象?

Flutter Hydrated Bloc 登录状态持久化问题解决

问题核心

已基于BLoC实现登录功能,LoginIdleState/LoginLoadingState/LoginLoadedState/LoginErrorState这几种状态的业务逻辑正常,但引入hydrated_bloc后无法正确持久化完整的登录状态对象。曾用单独Cubit存储布尔值实现简易持久化,但不够优雅,希望直接持久化Bloc的登录状态实体;同时对Dart无反射环境下的JSON转换逻辑存在困惑,现有代码的序列化/反序列化实现存在错误。

现有代码问题分析

  1. Bloc层JSON转换逻辑错误:
    • LoginServiceBloc的toJson直接返回{state}是无效的Map格式,无法将状态序列化为合法JSON
    • fromJson直接返回json['state']未做类型转换,无法将JSON反序列化为具体的LoginStateType子类实例
  2. 状态类设计缺陷:
    • 父类LoginStateType未做子类类型区分,反序列化时无法判断应该生成哪个状态子类
    • 部分子类的props重写错误(如LoginIdleState返回空数组),导致Equatable无法正确判断状态是否变化
    • LoginIdleState/LoginLoadingState的构造未合理处理responseModel参数,存在空值隐患
  3. ResponseModel序列化缺失:未确保ResponseModel及其子类具备完整的JSON转换能力,无法嵌套序列化到状态中

修正后的完整实现

第一步:完善ResponseModel与状态类的JSON转换

import 'package:equatable/equatable.dart';

// 基础响应模型,需实现JSON转换逻辑
abstract class ResponseModel extends Equatable {
  Map<String, dynamic> toJson();
  factory ResponseModel.fromJson(Map<String, dynamic> json) {
    // 通过type字段区分不同响应子类
    switch (json['type']) {
      case 'user':
        return UserResponseModel.fromJson(json);
      case 'error':
        return ErrorResponseModel.fromJson(json);
      default:
        throw Exception('未知的ResponseModel类型');
    }
  }
}

// 用户登录成功响应模型
class UserResponseModel extends ResponseModel {
  final String userId;
  final String token;

  UserResponseModel({required this.userId, required this.token});

  @override
  Map<String, dynamic> toJson() => {
        'type': 'user',
        'userId': userId,
        'token': token,
      };

  factory UserResponseModel.fromJson(Map<String, dynamic> json) => UserResponseModel(
        userId: json['userId'],
        token: json['token'],
      );

  @override
  List<Object?> get props => [userId, token];
}

// 登录错误响应模型
class ErrorResponseModel extends ResponseModel {
  final int code;
  final String msg;

  ErrorResponseModel({required this.code, required this.msg});

  @override
  Map<String, dynamic> toJson() => {
        'type': 'error',
        'code': code,
        'msg': msg,
      };

  factory ErrorResponseModel.fromJson(Map<String, dynamic> json) => ErrorResponseModel(
        code: json['code'],
        msg: json['msg'],
      );

  @override
  List<Object?> get props => [code, msg];
}

// 登录状态基类,统一处理序列化/反序列化
abstract class LoginStateType extends Equatable {
  final String type; // 用于区分状态子类
  final ResponseModel? responseModel;

  const LoginStateType(this.type, this.responseModel);

  // 反序列化入口,根据type生成对应状态子类
  factory LoginStateType.fromJson(Map<String, dynamic> json) {
    final responseJson = json['responseModel'] as Map<String, dynamic>?;
    final responseModel = responseJson != null ? ResponseModel.fromJson(responseJson) : null;

    switch (json['type']) {
      case 'idle':
        return const LoginIdleState();
      case 'loading':
        return const LoginLoadingState();
      case 'loaded':
        return LoginLoadedState(responseModel!);
      case 'error':
        return LoginErrorState(responseModel!);
      default:
        throw Exception('未知的LoginStateType类型');
    }
  }

  // 子类需实现的序列化方法
  Map<String, dynamic> toJson();

  @override
  List<Object?> get props => [type, responseModel];
}

// 空闲状态
class LoginIdleState extends LoginStateType {
  const LoginIdleState() : super('idle', null);

  @override
  Map<String, dynamic> toJson() => {'type': type, 'responseModel': null};
}

// 加载状态
class LoginLoadingState extends LoginStateType {
  const LoginLoadingState() : super('loading', null);

  @override
  Map<String, dynamic> toJson() => {'type': type, 'responseModel': null};
}

// 登录成功状态
class LoginLoadedState extends LoginStateType {
  const LoginLoadedState(super.responseModel) : super('loaded', super.responseModel);

  @override
  Map<String, dynamic> toJson() => {
        'type': type,
        'responseModel': responseModel?.toJson(),
      };
}

// 登录错误状态
class LoginErrorState extends LoginStateType {
  const LoginErrorState(super.responseModel) : super('error', super.responseModel);

  @override
  Map<String, dynamic> toJson() => {
        'type': type,
        'responseModel': responseModel?.toJson(),
      };
}

第二步:修正LoginServiceBloc的JSON转换逻辑

import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:logging/logging.dart';
import 'package:flutter/foundation.dart';

// 登录事件基类
abstract class LoginEventType extends Equatable {
  const LoginEventType();

  @override
  List<Object?> get props => [];
}

// 登录请求事件
class LoginRequestEvent extends LoginEventType {
  final String server;
  final String user;
  final String org;
  final String password;

  const LoginRequestEvent({
    required this.server,
    required this.user,
    required this.org,
    required this.password,
  });

  @override
  List<Object?> get props => [server, user, org, password];
}

// 登录错误事件
class LoginErrorEvent extends LoginEventType {
  final int code;
  final String msg;

  const LoginErrorEvent({required this.code, required this.msg});

  @override
  List<Object?> get props => [code, msg];
}

// 登录服务类
class LoginService {
  Future<ResponseModel> loginUser(String server, String user, String org, String password) async {
    // 替换为实际API调用逻辑
    await Future.delayed(const Duration(seconds: 1));
    return UserResponseModel(userId: 'user_123', token: 'fake_jwt_token');
    // return ErrorResponseModel(code: 401, msg: '账号或密码错误');
  }
}

// 登录业务Bloc
class LoginServiceBloc extends HydratedBloc<LoginEventType, LoginStateType> {
  LoginService? service;
  final Logger _logger = Logger('LoginServiceBloc');

  LoginServiceBloc(this.service) : super(const LoginIdleState()) {
    on<LoginRequestEvent>((event, emit) async {
      _logger.info('触发登录请求');
      emit(const LoginLoadingState());
      try {
        final response = await service!.loginUser(
          event.server,
          event.user,
          event.org,
          event.password,
        );
        if (response is UserResponseModel) {
          emit(LoginLoadedState(response));
        } else if (response is ErrorResponseModel) {
          emit(LoginErrorState(response));
        }
      } catch (e) {
        emit(LoginErrorState(ErrorResponseModel(code: 500, msg: e.toString())));
      }
    });

    on<LoginErrorEvent>((event, emit) {
      emit(LoginErrorState(ErrorResponseModel(code: event.code, msg: event.msg)));
    });
  }

  @override
  LoginStateType fromJson(Map<String, dynamic> json) {
    // 委托给状态类的工厂方法处理反序列化
    return LoginStateType.fromJson(json);
  }

  @override
  Map<String, dynamic>? toJson(LoginStateType state) {
    // 调用当前状态实例的序列化方法
    return state.toJson();
  }
}

第三步:初始化HydratedBloc存储

在应用入口初始化持久化存储:

import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化HydratedBloc存储目录
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: await getApplicationDocumentsDirectory(),
  );
  runApp(const MyApp());
}

关键注意事项

  1. 状态类型标识:通过type字段区分不同状态子类,是反序列化时能准确还原状态的核心
  2. 嵌套模型序列化:确保ResponseModel及其子类的JSON转换逻辑完整,支持嵌套序列化到状态中
  3. Equatable props:所有状态类的props需包含所有影响状态判断的字段,避免Equatable误判状态未变化
  4. 异常处理:在Bloc的事件处理中增加异常捕获,避免登录请求失败导致状态持久化中断

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 17:43:23