An extension to the bloc state management library which automatically persists and restores bloc states.

Overview

⚠️ Attention: This repository has been moved to https://github.com/felangel/bloc and is now read-only!

Hydrated Bloc

Pub Version Build Status Code Coverage style: effective dart MIT License Starware Bloc Library

An extension to the bloc state management library which automatically persists and restores bloc states and is built on top of hydrated_cubit.

Overview

hydrated_bloc exports a Storage interface which means it can work with any storage provider. Out of the box, it comes with its own implementation: HydratedStorage.

HydratedStorage is built on top of path_provider for a platform-agnostic storage layer. The out-of-the-box storage implementation reads/writes to file using the toJson/fromJson methods on HydratedBloc and should perform very well for most use-cases (performance reports coming soon). HydratedStorage is supported for desktop (example).

Usage

1. Use HydratedStorage

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  HydratedBloc.storage = await HydratedStorage.build();
  runApp(App());
}

2. Extend HydratedBloc and override fromJson/toJson

enum CounterEvent { increment, decrement }

class CounterBloc extends HydratedBloc<CounterEvent, int> {
  CounterBloc() : super(0);

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.decrement:
        yield state - 1;
        break;
      case CounterEvent.increment:
        yield state + 1;
        break;
    }
  }

  @override
  int fromJson(Map<String, dynamic> json) => json['value'] as int;

  @override
  Map<String, int> toJson(int state) => { 'value': state };
}

Now our CounterBloc is a HydratedBloc and will automatically persist its state. We can increment the counter value, hot restart, kill the app, etc... and our CounterBloc will always retain its state.

Custom Storage Directory

By default, all data is written to temporary storage which means it can be wiped by the operating system at any point in time.

An optional storageDirectory can be provided to override the default temporary storage directory:

HydratedBloc.storage = await HydratedStorage.build(
  storageDirectory: await getApplicationDocumentsDirectory(),
);

Custom Hydrated Storage

If the default HydratedStorage doesn't meet your needs, you can always implement a custom Storage by simply implementing the Storage interface and initializing HydratedBloc with the custom Storage.

// my_hydrated_storage.dart

class MyHydratedStorage implements Storage {
  @override
  dynamic read(String key) {
    // TODO: implement read
  }

  @override
  Future<void> write(String key, dynamic value) async {
    // TODO: implement write
  }

  @override
  Future<void> delete(String key) async {
    // TODO: implement delete
  }

  @override
  Future<void> clear() async {
    // TODO: implement clear
  }
}
// main.dart

HydratedBloc.storage = MyHydratedStorage();

Maintainers

Supporters

Starware

Hydrated Bloc is Starware.
This means you're free to use the project, as long as you star its GitHub repository.
Your appreciation makes us grow and glow up.

Comments
  • Support saving bloc's state instead of The state that bloc produces.

    Support saving bloc's state instead of The state that bloc produces.

    Is your feature request related to a problem? Please describe. I have a repositoryBloc that caches results. When using HydratedBloc, I can only store the output of the bloc, not it's internal private state. Describe the solution you'd like Don't require the fromJson method of HydratedBloc<E, S> to be of type S Describe alternatives you've considered I've considered customizing the output of toJson and fromJson, but it seems complicated and probably impossible. An alternative is to allow for persisting extra bloc data.

    question 
    opened by ThinkDigitalSoftware 15
  • HydratedBloc catches failures to serialize json and continues without yielding the new state

    HydratedBloc catches failures to serialize json and continues without yielding the new state

    Just found an issue where my HydratedBloc was rebuilding, but with the old states. There were no errors, and the state was being told to yield from the bloc correctly. Turns out the issue was that the toJson call was writing a non json-serializable map I.E. {'key1: ComplexClass} Following the logic would not have gotten me there with the debugger I appreciate it continuing to run, but I believe the state should still be yielded from Dart, even if it can't be yielded from json Basically, the write should fail, not the yield

    bug 
    opened by ThinkDigitalSoftware 12
  • Question: persistence time

    Question: persistence time

    Hi. First, awesome work with hydrated too.

    I have a question/issue :thinking:

    I'm using hydrated to store if it isn't the first time the app is opening. It works but after some time, like 4 o 5 days, the state is lost.

    I'm using hydrated incorrectly?

    class MainBloc extends HydratedBloc<MainEvent, MainState> {
      @override
      MainState get initialState =>
          super.initialState ?? MainState(true, false);
    
      @override
      Stream<MainState> mapEventToState(
        MainEvent event,
      ) async* {
        if (event is DisableFirstTime) {
          yield MainState(false, currentState.logged);
        }
      }
    
      void dispatchDisableFirstTime() {
        dispatch(DisableFirstTime());
      }
    
      @override
      MainState fromJson(Map<String, dynamic> json) =>
          MainState(json['firstTime'] as bool, json['logged'] as bool);
    
      @override
      Map<String, dynamic> toJson(MainState state) =>
          {'firstTime': state.firstTime, 'logged': state.logged};
    }
    
    class MainState {
      bool firstTime;
      bool logged;
    
      MainState(this.firstTime, this.logged);
    }
    
    class DisableFirstTime extends MainEvent {
      @override
      String toString() => 'DisableFirstTime';
    }
    
    

    Thank you for your time.

    question 
    opened by pblinux 12
  • Data lost when upgrading from 4.0.0 to 5.0.1

    Data lost when upgrading from 4.0.0 to 5.0.1

    Describe the bug After updating an app's dependencies to use hydrated_bloc 5.0.1, all saved data is lost from previous runs of the app. Verified on Android and iOS.

    To Reproduce Steps to reproduce the behavior:

    1. Run your app with hydrated_bloc 4.0.0
    2. Make some state changes which are saved via hydrated_bloc
    3. Re-run the app to see that the state changes were saved from the previous run
    4. Update the app to use hydrated_bloc 5.0.1
    5. Run the app and see that none of the previous state is retained

    Expected behavior Updating from 4.0.0 to 5.0.1 should be a non-destructive operation for users.

    Logs

    andre@Andres-MacBook-Pro gaming_news % flutter doctor -v
    [✓] Flutter (Channel beta, 1.19.0-4.3.pre, on Mac OS X 10.15.5 19F101, locale en-US)
        • Flutter version 1.19.0-4.3.pre at /Users/andre/Development/flutter
        • Framework revision 8fe7655ed2 (5 days ago), 2020-07-01 14:31:18 -0700
        • Engine revision 9a28c3bcf4
        • Dart version 2.9.0 (build 2.9.0-14.1.beta)
    
     
    [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
        • Android SDK at /Users/andre/Library/Android/sdk
        • Platform android-29, build-tools 28.0.3
        • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 11.5)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Xcode 11.5, Build version 11E608c
        • CocoaPods version 1.9.3
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 3.6)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin version 44.0.2
        • Dart plugin version 192.7761
        • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
    
    [✓] VS Code (version 1.46.1)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.12.1
    
    • No issues found!
    

    Additional context After some time investigating the issue, it is caused by the underlying migration to the hydrated_cubit library. That migration in itself isn't the problem, but in the commit to switch to hydrated_cubit, the backwards-compatible migration code was deleted.

    The file storage used for hydration in 4.0.0 was <directory>/.hydrated_bloc.json but was moved to <directory>/hydrated_box.hive[c] in 5.0.1. Commit 7cae45f8d83037250541865e69b8ebbce83eb2d0 had migration code for loading .hydrated_bloc.json if it existed.

    See:

    • Migration code added: https://github.com/felangel/hydrated_bloc/commit/7cae45f8d83037250541865e69b8ebbce83eb2d0#diff-03e6729a69de9f7669463016383f33f8
    • Migration code deleted: https://github.com/felangel/hydrated_bloc/commit/07261573db71cfdb3aa054099c167be9ecf5f109#diff-03e6729a69de9f7669463016383f33f8

    Workaround For other users experiencing this issue and needing a timely resolution, you can use you own custom hydrated storage as described in the README.

    The following workaround works for my use case with 5.0.1. It is based on HydratedStorage from 4.0.0 and does not use hive for storage. There are key changes to the read and write methods to support differences introduced in 5.0.0.

    class HydratedBlocStorageWorkaround implements HydratedStorage {
      HydratedBlocStorageWorkaround._(this._storage, this._file);
    
      static final _lock = Lock();
      final Map<String, dynamic> _storage;
      final File _file;
    
      static Future<HydratedBlocStorageWorkaround> getInstance({
        Directory storageDirectory,
      }) {
        return _lock.synchronized(() async {
          final directory = storageDirectory ?? await getTemporaryDirectory();
          final file = File('${directory.path}/.hydrated_bloc.json');
          var storage = <String, dynamic>{};
    
          if (await file.exists()) {
            try {
              storage = json.decode(await file.readAsString()) as Map<String, dynamic>;
            } on dynamic catch (_) {
              await file.delete();
            }
          }
    
          return HydratedBlocStorageWorkaround._(storage, file);
        });
      }
    
      @override
      dynamic read(String key) {
        final String jsonString = _storage[key];
        return jsonString?.isNotEmpty == true
            ? Map<String, dynamic>.from(json.decode(jsonString))
            : null;
      }
    
      @override
      Future<void> write(String key, dynamic value) {
        return _lock.synchronized(() {
          _storage[key] = json.encode(value);
          return _file.writeAsString(json.encode(_storage));
        });
      }
    
      @override
      Future<void> delete(String key) {
        return _lock.synchronized(() {
          _storage[key] = null;
          return _file.writeAsString(json.encode(_storage));
        });
      }
    
      @override
      Future<void> clear() {
        return _lock.synchronized(
          () async {
            _storage.clear();
            if (await _file.exists()) {
              await _file.delete();
            }
          },
        );
      }
    }
    
    question 
    opened by andre-arsenault 9
  • State is replicated in Hive file

    State is replicated in Hive file

    Describe the bug Each time a state transition happens a new copy of the state is appended to the Hive DB file hydrated_box.hive causing it to grow linearly in size.

    To Reproduce Steps to reproduce the behavior:

    1. Run the example app in the example folder
    2. Run adb shell in a terminal window
    3. Inside the adb shell run the command run-as com.example.example
    4. Run ls -l cache/hydrated_box.hive
    5. Press the plus button in the app
    6. Run ls -l cache/hydrated_box.hive again

    Expected behavior Size of hydrated_box.hive should be the same.

    enhancement 
    opened by tensor5 8
  • v3.1.0

    v3.1.0

    Status

    READY

    Breaking Changes

    NO

    Description

    • Update Hydrated Bloc to persist initialState upon initialization.
    • Update to latest effective_dart.
    • Add synchronous to prevent file corruption
    • Refactor HydratedBlocStorage.getInstance to avoid using a singleton
    • Upgrade to path_provider: ^1.6.5 for MacOS support
    • Invoke onError and continue emitting states when exceptions occur

    Todos

    • [X] Tests
    • [X] Documentation
    • [X] Examples

    Impact to Remaining Code Base

    • Non-Breaking changes which will be included in v3.1.0
    bug enhancement 
    opened by felangel 8
  • Web support

    Web support

    Status

    READY

    Breaking Changes

    NO

    Description

    Support for Hydrated Bloc on the Web via html.window.localStorage

    Survives browser restarts!

    Related PRs

    N/A

    Todos

    • [X] Tests
    • [ ] Documentation
    • [X] Examples

    Steps to Test or Reproduce

    Outline the steps to test or reproduce the PR here.

    flutter run -d chrome
    

    Impact to Remaining Code Base

    This PR will affect:

    • Support for Flutter Web
    enhancement 
    opened by rodydavis 8
  • Hydrated Bloc yields the error state

    Hydrated Bloc yields the error state

    Describe the bug So i switched from flutterbloc to hydaretdbloc, when i my switch on the data connection and i do not have an active internet connection the hydrated bloc works but when i complete shutdown the WiFi & the data connection it does not work

    Expected behavior Load the cached rest data rather than yielding the error state

    question 
    opened by iampato 7
  • Adding Support for Desktop

    Adding Support for Desktop

    Status

    READY

    Breaking Changes

    NO

    Description

    Support for Desktop Directory to Story Application Data

    Related PRs

    N/A

    Todos

    • [x] Tests
    • [x] Documentation
    • [X] Examples

    Steps to Test or Reproduce

    Outline the steps to test or reproduce the PR here.

    Open project in VSCode and click run for MacOS, Linux or Windows
    

    Impact to Remaining Code Base

    This PR will affect:

    • Saving
    enhancement 
    opened by rodydavis 7
  • RuntimeHydratedBloc implementation

    RuntimeHydratedBloc implementation

    Status

    READY

    Breaking Changes

    NO

    Description

    In large and complex applications sometimes you want persist the state only during the runtime. Because UX, optimization or whatever, you do not want to persist in the device memory the state.

    The implementation is very simple and I think it makes sense to offer this possibility in this library. Also, in the future the common functionality could be refactored in base/utils class.

    Todos

    • [X] Tests
    • [X] Documentation
    • [X] Examples
    opened by nfdz 6
  • Unhandled Exception: HiveError: Cannot write, unknown type: _$Orange. Did you forget to register an adapter?

    Unhandled Exception: HiveError: Cannot write, unknown type: _$Orange. Did you forget to register an adapter?

    I am using hydrated_bloc with Freezed package.

    State is:

    @freezed
    abstract class ThemeState with _$ThemeState {
      const factory ThemeState({
        @required ThemeScheme theme,
        @required ThemeBrightness brightness,
      }) = _ThemeState;
    
      factory ThemeState.fromJson(Map<String, dynamic> json) =>
          _$ThemeStateFromJson(json);
    }
    

    ThemeScheme is:

    @freezed
    abstract class ThemeScheme with _$ThemeScheme {
      const factory ThemeScheme.orange() = Orange;
      const factory ThemeScheme.blue() = Blue;
    
      factory ThemeScheme.fromJson(Map<String, dynamic> json) =>
          _$ThemeSchemeFromJson(json);
    }
    

    ThemeBrightness is:

    @freezed
    abstract class ThemeBrightness with _$ThemeBrightness {
      const factory ThemeBrightness.light() = Light;
      const factory ThemeBrightness.dark() = Dark;
    
      factory ThemeBrightness.fromJson(Map<String, dynamic> json) =>
          _$ThemeBrightnessFromJson(json);
    }
    

    and:

    @override
      ThemeState fromJson(Map<String, dynamic> json) {
        try {
          final state = ThemeState.fromJson(json);
          return state;
        } catch (_) {
          return null;
        }
      }
    
      @override
      Map<String, dynamic> toJson(ThemeState state) {
        return state.toJson();
      }
    

    but I get :

    E/flutter (22951): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: HiveError: Cannot write, unknown type: _$Orange. Did you forget to register an adapter?
    E/flutter (22951): #0      BinaryWriterImpl.write 
    package:hive/…/binary/binary_writer_impl.dart:310
    E/flutter (22951): #1      BinaryWriterImpl.writeMap 
    package:hive/…/binary/binary_writer_impl.dart:215
    E/flutter (22951): #2      BinaryWriterImpl.write 
    package:hive/…/binary/binary_writer_impl.dart:306
    E/flutter (22951): #3      BinaryWriterImpl.writeFrame 
    package:hive/…/binary/binary_writer_impl.dart:254
    E/flutter (22951): #4      StorageBackendVm.writeFrames.<anonymous closure> 
    package:hive/…/vm/storage_backend_vm.dart:123
    E/flutter (22951): #5      ReadWriteSync.syncWrite.<anonymous closure> 
    package:hive/…/vm/read_write_sync.dart:26
    E/flutter (22951): #6      _rootRunUnary  (dart:async/zone.dart:1192:38)
    E/flutter (22951): #7      _CustomZone.runUnary  (dart:async/zone.dart:1085:19)
    E/flutter (22951): #8      _FutureListener.handleValue  (dart:async/future_impl.dart:141:18)
    E/flutter (22951): #9      Future._propagateToListeners.handleValueCallback  (dart:async/future_impl.dart:686:45)
    E/flutter (22951): #10     Future._propagateToListeners  (dart:async/future_impl.dart:715:32)
    E/flutter (22951): #11     Future._addListener.<anonymous closure>  (dart:async/future_impl.dart:391:9)
    E/flutter (22951): #12     _rootRun  (dart:async/zone.dart:1184:13)
    E/flutter (22951): #13     _CustomZone.run  (dart:async/zone.dart:1077:19)
    E/flutter (22951): #14     _CustomZone.runGuarded  (dart:async/zone.dart:979:7)
    E/flutter (22951): #15     _CustomZone.bindCallbackGuarded.<anonymous closure>  (dart:async/zone.dart:1019:23)
    E/flutter (22951): #16     _microtaskLoop  (dart:async/schedule_microtask.dart:43:21)
    E/flutter (22951): #17     _startMicrotaskLoop  (dart:async/schedule_microtask.dart:52:5)
    E/flutter (22951):
    
    question 
    opened by mohammadne 5
Releases(v5.0.3)
Owner
Felix Angelov
software engineer by day, software engineer by night.
Felix Angelov
GoodBudget - A budget monitor or expense tracker Flutter application that persists data with Hive NoSQL database.

GoodBudget - A budget monitor or expense tracker Flutter application that persists data with Hive NoSQL database. This cross platform application is available on Android, iOS & Web. Both expenses and income are monitored.

Sherida Providence 1 Sep 19, 2022
flutter_bloc state management extension that integrates sealed_unions.

flutter_bloc meets sealed_unions Sponsors Our top sponsors are shown below! [Become a Sponsor] Try the Flutter Chat Tutorial ?? Quick Start Extend Uni

Felix Angelov 70 Aug 16, 2022
State Persistence - Persist state across app launches. By default this library store state as a local JSON file called `data.json` in the applications data directory. Maintainer: @slightfoot

State Persistence Persist state across app launches. By default this library store state as a local JSON file called data.json in the applications dat

Flutter Community 70 Sep 28, 2022
Practice building basic animations in apps along with managing app state by BLoC State Management, Flutter Slider.

Practice building basic animations in apps along with managing app state by BLoC State Management including: Cubit & Animation Widget, Flutter Slider.

TAD 1 Jun 8, 2022
A powerful official extension library of Tab/TabBar/TabView, which support to scroll ancestor or child Tabs when current is overscroll, and set scroll direction and cache extent.

extended_tabs Language: English | 中文简体 A powerful official extension library of Tab/TabBar/TabView, which support to scroll ancestor or child Tabs whe

FlutterCandies 185 Dec 13, 2022
🎯 This library automatically generates object classes from JSON files that can be parsed by the freezed library.

The Most Powerful Way to Automatically Generate Model Objects from JSON Files ⚡ 1. Guide ?? 1.1. Features ?? 1.1.1. From 1.1.2. To 1.2. Getting Starte

KATO, Shinya / 加藤 真也 14 Nov 9, 2022
Flutter bloc example - An app for State management using BLoC pattern in Flutter

Flutter BLoC My first app for State management using BLoC pattern in Flutter Col

Harshil Patel 1 Jun 16, 2022
💻 Flutter clean architecture using the bloc & cubit library for state management

Egymation ?? · This application was developed using a well-defined and decoupled architecture, following TDD (test-driven programming) as a working me

Mohaned Zekry 3 Nov 21, 2022
A Flutter Material Button that animates between Progress and Error states

progress_button A Material Flutter Button that supports progress and error visuals Getting Started ProgressButton is designed to be easy to use and cu

Halil Ozercan 132 Sep 21, 2022
Flutter Control is complex library to maintain App and State management. Library merges multiple functionality under one hood. This approach helps to tidily bound separated logic into complex solution.

Flutter Control is complex library to maintain App and State management. Library merges multiple functionality under one hood. This approach helps to

Roman Hornak 23 Feb 23, 2022
A flutter package that provides multiple states for a button with endless customizability.

multi_state_button A package which provides multiple states for a button with endless customizability. Getting Started Add dependency dependencies:

null 4 Apr 11, 2022
A tool which automatically generates Flutter localization resources from CSV and Excel files.

flappy_translator A tool which automatically generates Flutter localization resources from CSV and Excel files. This is especially useful as any team

Smart&Soft 55 Sep 15, 2022
Shopify Tag and Product Management App using Flutter and Riverpod State Management

Myshopify App A Simple Flutter Application project to get List of Tags, Products and Product Details from shopify https://shopicruit.myshopify.com/adm

Idowu Tomiwa 5 Nov 12, 2022
Purpose of this project is to create extendable architecture of making platform aware Widgets which automatically select platform specific implementation

Old good factory Main obstacle in creating native experience on Flutter is the fact that you are asked to rebuild two layouts using platform specific

Swav Kulinski (Robotoaster) 101 Oct 14, 2022
A powerful state machine for MobX management, that can be used in almost any application state.

A powerful state machine for MobX management, which can be used in almost any application state. It has 3 states - loading, success, error - and is pe

Daniel Magri 8 Oct 31, 2022
Petrus Nguyễn Thái Học 193 Dec 29, 2022
This project follows the Reso Coder course for flutter test-driven-development with clean architecture and BloC state management for a random trivia simple app.

This project follows the Reso Coder course for flutter test-driven-development with clean architecture and BloC state management for a random trivia simple app.

Tomas B Sarmiento Abella 1 Jan 5, 2022