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
相关产品推荐
相关产品推荐

