Flutter State Restoration with Bloc: A Practical Guide

This article provides a practical solution for restoring the state of your Bloc in a Flutter application after the app is killed by the system. It addresses the limitations of the current state restoration mechanisms and offers a detailed approach.

Understanding the Problem

Flutter’s built-in state restoration mechanisms are generally designed for StatefulWidgets. This creates a challenge when integrating with the Bloc architecture, which relies on state management that might not be easily adaptable to the RestorationBucket.

The core issue is that the emit method within Bloc is marked as @visibleForTesting, and therefore should not be called directly from outside the Bloc class. This generates a warning when restoring the state. Direct use of the RestorationManager‘s rootBucket is not ideal due to the need to use Future. A better alternative is to pass the RestorationBucket directly into the Bloc.

Proposed Solution

To seamlessly restore Bloc state, create a custom RestorableBloc class that extends Bloc. This class will handle state serialization, deserialization, and interaction with the RestorationBucket:

Custom RestorableBloc Class


import 'package:bloc/bloc.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:restoration_scope/restoration_scope.dart';



abstract class RestorableBloc extends Bloc
    with RestorationMixin {
  RestorableBloc({
    required this.restorationId,
    required this.restorationBucket,
  }) : super(initialState);

   final String restorationId;
   final RestorationBucket restorationBucket;

  @override
  void restoreState(RestorationBucket? restoration) {
    final Map? stateMap =
        restoration?.get(restorationId) as Map?;

    if (stateMap != null) {
      final restoredState = _fromMap(stateMap);
      emit(restoredState);
    }

    super.restoreState(restoration); // Ensure the rest of the base class is restored
  }

  @override
  void dispose() {
    super.dispose();
  }

  State _fromMap(Map map) {
    // Implementation for converting Map to State
    // Replace with your actual conversion logic
    return (State) as State;
  }

  Map _toMap(State state) {
    // Implementation for converting State to Map
    // Replace with your actual conversion logic
    return {}; // Or {} as appropriate
  }

  @override
  RestorationBucket? onSave() {
    final map = _toMap(state);
    return restorationBucket.set(restorationId, map);
  }
}

Usage Example


// In your BlocProvider
BlocProvider(
  create: (context) {
    final restorationBucket = RestorationScope.maybeOf(context)?.restorationBucket;
    if (restorationBucket == null) {
      throw Exception("RestorationBucket not found in context.");
    }
    return MyBloc(restorationId: 'my_bloc', restorationBucket: restorationBucket);
  },
  child: //Your Bloc UI
)

Error Handling and Considerations

  • Null Checks: Crucially, always check for null values when retrieving data from the RestorationBucket to prevent crashes.
  • Type Safety: Ensure that your _fromMap and _toMap methods handle the conversion of your State object to and from Map correctly and safely.

By implementing this RestorableBloc class, you’ll be able to restore your Bloc’s state efficiently and reliably after the app is killed, thereby resolving the issues with the direct use of RestorationManager.rootBucket.

Remember to replace the placeholders with your actual state and conversion logic. Thorough testing is essential to ensure the state restoration process works correctly in different scenarios.