This article provides a practical solution for restoring the state of your Flutter Bloc when your app is killed by the system. It addresses the common issues of restoring Bloc state within a StatefulWidget context, while avoiding the use of persisting Bloc state globally.
Understanding the Problem
Restoring application state after a system kill or configuration change is crucial for maintaining user experience. Flutter’s built-in state restoration mechanism, however, doesn’t natively support Blocs. While HydratedBloc
offers a persistence solution, it might not be the optimal choice if you don’t need state persistence throughout the app’s lifecycle. The built-in mechanism is limited to StatefulWidgets
, and a standardized way for managing Bloc state restoration is lacking.
Solution: Custom RestorableBloc
Implementing a custom RestorableBloc
can provide a robust and flexible approach to state restoration without global persistence.
1. Abstract Class RestorableBloc
import 'package:bloc/bloc.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
abstract class RestorableBloc extends Bloc {
final String restorationId;
final RestorationBucket bucket;
RestorableBloc(
this.restorationId,
this.bucket,
) : super(initialData);
// Method to deserialize the state from the restoration bucket.
State deserializeState(Map data);
// Method to serialize the current state into a Map.
Map serializeState(State state);
@override
Future close() {
// Important: call super.close()
return super.close();
}
@override
void onChange(Change change) {
super.onChange(change);
// Update the bucket.
bucket.addRestorable(
restorationId,
serializeState(state),
);
}
}
2. Implementing Serialization/Deserialization
// Example implementation (replace with your data types)
@override
State deserializeState(Map data) {
// ... (Deserialize from the map) ...
return initialData;
}
@override
Map serializeState(State state) {
// ... (Serialize the state to a map) ...
return {};
}
3. Using RestorationScope
// In your widget where you create the Bloc:
RestorableBloc(
'myBloc',
// Get restoration bucket using context.
RestorationScope.maybeOf(context)?.bucket ?? RestorationBucket()
)
4. Handling the @visibleForTesting
warning
The warning regarding emit
is a result of internal Bloc state change mechanism. Don’t override the emit
method directly but instead call the super method.
// ... inside your RestorableBloc ...
@override
void emit(covariant State state) {
super.emit(state);
}
Potential Errors and Solutions
- Error: Incorrect state serialization/deserialization.
Solution: Double-check that the conversion methods correctly transform your state data (e.g., dates, lists, complex objects). Ensure data types in theMap
match what you expect during deserialization. Use debugging tools to inspect data. - Error: Issues with
RestorationScope
.
Solution: Ensure theRestorationScope
is correctly placed in your widget tree, and that theRestorationBucket
is accessible. UseRestorationScope.maybeOf(context)
to safely check for its presence. - Error: Missing
super.close()
inclose()
method.
Solution: Callingsuper.close()
in yourRestorableBloc
‘sclose()
method is crucial for proper cleanup of the underlying state.
Conclusion
By implementing a RestorableBloc
, you can effectively restore the state of your Flutter Blocs after your app is killed by the system, enabling a smoother user experience. This approach allows for state restoration without resorting to global state persistence.