Hydrated Bloc重启/热重载后无法持久化状态的问题
问题描述
通过单选按钮切换并更新PrayerTypeBloc状态时,点击操作可正常变更状态,但热重载或重启应用后,状态无法保留。已使用hydrated_bloc库,相关配置与代码如下:
pubspec.yaml 依赖配置
dependencies: bloc: ^8.1.4 equatable: ^2.0.5 flutter: sdk: flutter flutter_bloc: ^8.1.5 http: ^1.2.0 hydrated_bloc: ^9.1.5 path_provider: ^2.1.3 permission_handler: ^11.3.1
代码文件
1. prayer_type_bloc.dart
import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:meta/meta.dart'; part 'prayer_type_event.dart'; part 'prayer_type_state.dart'; class PrayerTypeBloc extends HydratedBloc<PrayerTypeEvent, PrayerTypeState> { PrayerTypeBloc() : super(const PrayerTypeInitial()) { on<LocationSelected>( (LocationSelected event, Emitter emit) => emit(const LocationSelectedState()), ); on<GpsSelected>( (GpsSelected event, Emitter emit) => emit(const GpsSelectedState()), ); } @override PrayerTypeState? fromJson(Map<String, dynamic> json) { if (json['prayer_type'] == null) { return null; } switch (json['prayer_type']) { case 'LocationSelectedState': return const LocationSelectedState(); case 'GpsSelectedState': return const GpsSelectedState(); default: return null; } } @override Map<String, dynamic>? toJson(PrayerTypeState state) { return {'prayer_type': state.toString()}; } }
2. prayer_type_event.dart
part of 'prayer_type_bloc.dart'; @immutable sealed class PrayerTypeEvent extends Equatable { const PrayerTypeEvent(); @override List<Object?> get props => []; } class LocationSelected extends PrayerTypeEvent { const LocationSelected(); } class GpsSelected extends PrayerTypeEvent { const GpsSelected(); }
3. prayer_type_state.dart
part of 'prayer_type_bloc.dart'; @immutable sealed class PrayerTypeState extends Equatable { const PrayerTypeState(); @override List<Object?> get props => []; } class PrayerTypeInitial extends PrayerTypeState { const PrayerTypeInitial(); } class LocationSelectedState extends PrayerTypeState { const LocationSelectedState(); } class GpsSelectedState extends PrayerTypeState { const GpsSelectedState(); }
4. main.dart
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:muezzyn/app_bloc_observer.dart'; import 'package:muezzyn/bloc/prayer_type/bloc/prayer_type_bloc.dart'; import 'package:muezzyn/cubit/bottom_nav_cubit.dart'; import 'package:muezzyn/widget/main_wrapper.dart'; import 'package:path_provider/path_provider.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); Bloc.observer = AppBlocObserver(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: await getTemporaryDirectory(), ); runApp(MainApp()); } class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: MultiBlocProvider( providers: [ BlocProvider(create: (context) => BottomNavCubit()), BlocProvider<PrayerTypeBloc>(create: (context) => PrayerTypeBloc()), ], child: MainWrapper(), ), ); } }
Settings_Page.dart
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:muezzyn/bloc/prayer_type/bloc/prayer_type_bloc.dart'; class SettingsPage extends StatefulWidget { const SettingsPage({super.key}); @override State<SettingsPage> createState() => _SettingsPageState(); } class _SettingsPageState extends State<SettingsPage> { PrayerTypeBloc? _prayerTypeBloc; @override void initState() { super.initState(); _prayerTypeBloc = BlocProvider.of<PrayerTypeBloc>(context); } @override Widget build(BuildContext context) { return Scaffold( body: BlocBuilder<PrayerTypeBloc, PrayerTypeState>( bloc: _prayerTypeBloc!, builder: (context, state) { PrayerTypeState currentState = state; return Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Location Preference"), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Radio<PrayerTypeState>( value: const LocationSelectedState(), groupValue: currentState, onChanged: (value) { if (value != null) { context .read<PrayerTypeBloc>() .add(LocationSelected()); } }, ), Text('Location'), Radio<PrayerTypeState>( value: const GpsSelectedState(), groupValue: currentState, onChanged: (value) { if (value != null) { context.read<PrayerTypeBloc>().add(GpsSelected()); } }, ), Text('GPS'), ], ), ], ), ); }, ), ); } }
问题解决方法
1. 更换持久化存储目录
当前使用的getTemporaryDirectory()是临时目录,系统会随时清理其中数据,导致重启后状态丢失。替换为应用专属的持久化目录:
// 修改main.dart中的存储目录配置 HydratedBloc.storage = await HydratedStorage.build( storageDirectory: await getApplicationDocumentsDirectory(), );
2. 修复状态序列化/反序列化逻辑
state.toString()返回的字符串可能包含额外信息(如内存地址),导致反序列化匹配失败。给每个状态定义唯一标识:
- 更新
prayer_type_state.dart:
@immutable sealed class PrayerTypeState extends Equatable { const PrayerTypeState(); // 新增状态唯一标识字段 String get type; @override List<Object?> get props => []; } class PrayerTypeInitial extends PrayerTypeState { const PrayerTypeInitial(); @override String get type => 'PrayerTypeInitial'; } class LocationSelectedState extends PrayerTypeState { const LocationSelectedState(); @override String get type => 'LocationSelectedState'; } class GpsSelectedState extends PrayerTypeState { const GpsSelectedState(); @override String get type => 'GpsSelectedState'; }
- 更新
prayer_type_bloc.dart的序列化方法:
@override PrayerTypeState? fromJson(Map<String, dynamic> json) { final type = json['prayer_type'] as String?; switch (type) { case 'LocationSelectedState': return const LocationSelectedState(); case 'GpsSelectedState': return const GpsSelectedState(); default: return const PrayerTypeInitial(); // 匹配失败时返回初始状态 } } @override Map<String, dynamic>? toJson(PrayerTypeState state) { return {'prayer_type': state.type}; }
3. 完善初始状态处理
原fromJson方法在匹配失败时返回null,会触发HydratedBloc使用默认初始状态,明确返回PrayerTypeInitial可避免潜在问题。
内容的提问来源于stack exchange,提问作者Auvee
相关产品推荐
相关产品推荐

