Flutter Bloc State Restoration: A Practical Guide

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 the Map match what you expect during deserialization. Use debugging tools to inspect data.
  • Error: Issues with RestorationScope.

    Solution: Ensure the RestorationScope is correctly placed in your widget tree, and that the RestorationBucket is accessible. Use RestorationScope.maybeOf(context) to safely check for its presence.
  • Error: Missing super.close() in close() method.

    Solution: Calling super.close() in your RestorableBloc‘s close() 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.