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

Hydrated Bloc异常:APP重启无网时数据无法持久化且报错

Flutter Hydrated Cubit 数据持久化异常排查

问题背景

开发Flutter应用时,需实现数据持久化,确保APP重启无网络时旧数据仍能显示。其他Hydrated Bloc功能正常,但AttendanceStatusCubit无法正常工作:重启APP时报类型转换错误,UI构建时触发空指针异常。

相关代码

1. StatusVerificationModel类

class StatusVerificationModel extends Equatable {
  final List<Map<String, dynamic>> statusList;

  const StatusVerificationModel({required this.statusList});

  factory StatusVerificationModel.fromMap(Map<String, dynamic> map) {
    List<Map<String, dynamic>> status = [];

    if(map['status'] != "") {
      status = List<Map<String, dynamic>>.from(map['status'])
          .map((statusItem) => {
            "id": statusItem['id'] ?? "",
            "editable": statusItem['editable'] ?? "",
            "status": statusItem['status'] ?? "",
            "date_created": statusItem['date_created'],
            "name": statusItem['name'],
            "start_date": statusItem['start_date'] ?? "",
            "end_date": statusItem['end_date'] ?? "",
            "start_time": statusItem['start_time'] ?? "",
            "end_time": statusItem['end_time'] ?? "",
            "justification": statusItem['justification'] ?? "",
            "reasons": statusItem['reasons'] ?? "",
            "fileNames": List<String>.from(statusItem['file_names']),
            "filePaths": List<String>.from(statusItem['file_paths']),
          })
      .toList();
    }

    return StatusVerificationModel(
      statusList: status,
    );
  }

  factory StatusVerificationModel.fromJson(Map<String, dynamic> json) {
    List<Map<String, dynamic>> status = [];

    if(json['status'] != "") {
      status = List<Map<String, dynamic>>.from(json['status'])
          .map((statusItem) => {
            "id": statusItem['id'] ?? "",
            "editable": statusItem['editable'] ?? "",
            "status": statusItem['status'] ?? "",
            "date_created": statusItem['date_created'],
            "name": statusItem['name'],
            "start_date": statusItem['start_date'] ?? "",
            "end_date": statusItem['end_date'] ?? "",
            "start_time": statusItem['start_time'] ?? "",
            "end_time": statusItem['end_time'] ?? "",
            "justification": statusItem['justification'] ?? "",
            "reasons": statusItem['reasons'] ?? "",
            "fileNames": List<String>.from(statusItem['file_names']),
            "filePaths": List<String>.from(statusItem['file_paths']),
          })
      .toList();
    }

    return StatusVerificationModel(
      statusList: status,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      "status": statusList,
    };
  }

  Map<String, dynamic> toMap() {
    return {
      "status": statusList,
    };
  }
}

2. AttendanceStatusState类

abstract class AttendanceStatusState extends Equatable {
  const AttendanceStatusState();

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

class AttendanceStatusInitial extends AttendanceStatusState {}

class AttendanceStatusLoading extends AttendanceStatusState {}

class AttendanceStatusLoaded extends AttendanceStatusState {
  final StatusVerificationModel? attendanceStatus;
  final StatusVerificationModel? attendanceVerification;

  const AttendanceStatusLoaded({this.attendanceStatus, this.attendanceVerification});

  Map<String, dynamic> toMap() {
    return {
      "attendanceStatus": attendanceStatus?.toMap(),
      "attendanceVerification": attendanceVerification?.toMap(),
    };
  }

  Map<String, dynamic> toJson() {
    return {
      "attendanceStatus": attendanceStatus?.toJson(),
      "attendanceVerification": attendanceVerification?.toJson(),
    };
  }

  factory AttendanceStatusLoaded.fromMap(Map<String, dynamic> map) {
    return AttendanceStatusLoaded(
      attendanceStatus: StatusVerificationModel.fromJson(map['attendanceStatus']),
      attendanceVerification: StatusVerificationModel.fromJson(map['attendanceVerification']),
    );
  }

  factory AttendanceStatusLoaded.fromJson(String source) =>
      AttendanceStatusLoaded.fromMap(json.decode(source));
}

3. AttendanceStatusCubit类

class AttendanceStatusCubit extends HydratedCubit<AttendanceStatusState> {
  final UserRepository _userRepository;
  final AttendanceStatusRepository _attendanceStatusRepository;

  StatusVerificationModel? lastLoadedAttendanceStatus;
  StatusVerificationModel? lastLoadedAttendanceVerification;

  AttendanceStatusCubit({required UserRepository userRepository, required AttendanceStatusRepository attendanceStatusRepository})
      : _userRepository = userRepository, _attendanceStatusRepository = attendanceStatusRepository,
        super(AttendanceStatusInitial()) {
    AttendanceStatusInitialized();
  }

  void AttendanceStatusInitialized() async {
    emit(AttendanceStatusLoading());

    var user = await _userRepository.getUser();

    if(user != null) {
      try {
        var attendanceStatus = await _attendanceStatusRepository.getData(id: user.id);
        var attendanceVerification = await _attendanceStatusRepository.getVerificationData(id: user.id);
        lastLoadedAttendanceStatus = attendanceStatus;
        lastLoadedAttendanceVerification = attendanceVerification;
        emit(AttendanceStatusLoaded(attendanceStatus: attendanceStatus, attendanceVerification: attendanceVerification));
      } catch (e) {
        emit(const AttendanceStatusError(error: "Error: No Internet Connection"));
        emit(AttendanceStatusLoaded(attendanceStatus: lastLoadedAttendanceStatus, attendanceVerification: lastLoadedAttendanceVerification));
      }
    } else {
      emit(const AttendanceStatusEmpty(message: "Session ended, please login to continue"));
    }
  }
 @override
  AttendanceStatusState? fromJson(Map<String, dynamic> json) {
   return AttendanceStatusLoaded.fromMap(json);
  }

  @override
  Map<String, dynamic>? toJson(AttendanceStatusState state) {
    if(state is AttendanceStatusLoaded) {
      lastLoadedAttendanceVerification = state.attendanceVerification;
      lastLoadedAttendanceStatus = state.attendanceStatus;

      return state.toMap();
    }
    return null;
  }

}

报错信息

重启APP时的报错

I/flutter (14491): AttendanceStatusCubit type 'Null' is not a subtype of type 'Map<String, dynamic>'

UI构建时的异常

======== Exception caught by widgets library =======================================================
The following NoSuchMethodError was thrown building BlocBuilder<AttendanceStatusCubit, AttendanceStatusState>(dirty, dependencies: [_InheritedProviderScope<AttendanceStatusCubit?>], state: _BlocBuilderBaseState<AttendanceStatusCubit, AttendanceStatusState>#06944):
The getter 'statusList' was called on null.
Receiver: null
Tried calling: statusList

The relevant error-causing widget was: 
  BlocBuilder<AttendanceStatusCubit, AttendanceStatusState> BlocBuilder:file:///C:/Users/USER/Desktop/Android%20Studio/HPCS/KPM/KPM/lib/screens/pages/status_verification.dart:53:20
When the exception was thrown, this was the stack: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:38:5)
#1      _StatusVerificationState._createDataTable (package:kpm_scanme/screens/pages/status_verification.dart:230:34)
#2      _StatusVerificationState.paginatedDataTable (package:kpm_scanme/screens/pages/status_verification.dart:216:13)
#3      _StatusVerificationState.mainContent (package:kpm_scanme/screens/pages/status_verification.dart:131:11)
#4      _StatusVerificationState.mainUI (package:kpm_scanme/screens/pages/status_verification.dart:81:15)
#5      _StatusVerificationState.build.<anonymous closure> (package:kpm_scanme/screens/pages/status_verification.dart:60:26)
#6      BlocBuilder.build (package:flutter_bloc/src/bloc_builder.dart:90:57)

问题原因与修复方案

1. 反序列化未处理Null值

AttendanceStatusLoaded.fromMap中直接调用StatusVerificationModel.fromJson(map['attendanceStatus']),但持久化数据中attendanceStatus或attendanceVerification可能为Null,导致类型转换失败。

修复代码:

factory AttendanceStatusLoaded.fromMap(Map<String, dynamic> map) {
  return AttendanceStatusLoaded(
    attendanceStatus: map['attendanceStatus'] != null 
        ? StatusVerificationModel.fromJson(map['attendanceStatus']) 
        : null,
    attendanceVerification: map['attendanceVerification'] != null 
        ? StatusVerificationModel.fromJson(map['attendanceVerification']) 
        : null,
  );
}

2. 初始化逻辑覆盖持久化状态

Hydrated Cubit初始化时会自动从本地恢复状态,但你在构造函数中直接调用AttendanceStatusInitialized(),立即emitAttendanceStatusLoading覆盖了恢复的AttendanceStatusLoaded状态,导致无网络时旧数据丢失。

修复代码:

AttendanceStatusCubit({required UserRepository userRepository, required AttendanceStatusRepository attendanceStatusRepository})
    : _userRepository = userRepository, _attendanceStatusRepository = attendanceStatusRepository,
      super(AttendanceStatusInitial()) {
  // 延迟执行初始化,仅当状态为初始状态时才请求数据
  Future.microtask(() {
    if (state is AttendanceStatusInitial) {
      AttendanceStatusInitialized();
    }
  });
}

3. UI未处理Null状态

UI直接访问attendanceStatus!.statusList,但attendanceStatus可能为Null(首次启动无数据、反序列化后为空),触发空指针异常。

修复方案:
在BlocBuilder中添加Null判断:

BlocBuilder<AttendanceStatusCubit, AttendanceStatusState>(
  builder: (context, state) {
    if (state is AttendanceStatusLoaded) {
      final statusList = state.attendanceStatus?.statusList ?? [];
      if (statusList.isEmpty) {
        return const Center(child: Text("无数据"));
      }
      return paginatedDataTable(statusList);
    }
    if (state is AttendanceStatusLoading) {
      return const CircularProgressIndicator();
    }
    return const Center(child: Text("请登录"));
  },
)

4. 额外优化:StatusVerificationModel空值判断

原代码中map['status'] != ""的判断逻辑不合理,应改为判断是否为非空数组:

if(map['status'] != null && map['status'] is List && (map['status'] as List).isNotEmpty) {
  // 处理数据逻辑
}

内容的提问来源于stack exchange,提问作者rhanif.rizal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 07:22:03