bloc_signals_hydrate 0.9.0
bloc_signals_hydrate: ^0.9.0 copied to clipboard
State persistence and hydration adapters for BlocSignal state containers.
|
⚡ bloc_signals_hydrate"With the rigor of BLoC and the flex and speed of Signals"
State persistence and hydration adapters for |
HydratedCubitSignal and HydratedBlocSignal automatically persist state changes to storage and restore state synchronously during container instantiation across app restarts.
🌐 Ecosystem Packages #
The BlocSignal monorepo consists of 10 modular packages:
⚡ Key Features #
- 📦
dynamic/Object?JSON Support:fromJson(dynamic json)andtoJson(StateType state)accept primitives (num,String,bool,List,Map). Primitive states do not require map wrappers like{"value": 42}! - ⚡ Synchronous Initial Hydration: State is restored synchronously during constructor execution—meaning initial widget builds render hydrated data immediately with zero frame flicker.
- 🛠️ Zero-Dependency Default: Ships with
MemoryHydratedStoragefor fast in-memory unit testing out-of-the-box.
🚀 Getting Started #
Add bloc_signals_hydrate to your pubspec.yaml:
dependencies:
bloc_signals: ^0.2.5
bloc_signals_hydrate: ^0.1.1
💡 Quick Examples #
1. Primitive State Hydration (HydratedCubitSignal) #
import 'package:bloc_signals_hydrate/bloc_signals_hydrate.dart';
class CounterCubit extends HydratedCubitSignal<int> {
CounterCubit() : super(initialState: 0);
void increment() => emit(stateValue + 1);
@override
int? fromJson(dynamic json) => json as int?;
@override
dynamic toJson(int state) => state; // Return primitive directly!
}
2. Complex Object Hydration (HydratedBlocSignal) #
import 'package:bloc_signals_hydrate/bloc_signals_hydrate.dart';
class UserCubit extends HydratedCubitSignal<UserModel> {
UserCubit() : super(initialState: UserModel.anonymous);
@override
UserModel? fromJson(dynamic json) {
if (json is Map<String, dynamic>) {
return UserModel.fromJson(json);
}
return null;
}
@override
dynamic toJson(UserModel state) => state.toJson();
}
3. Storage Keys & Instance Scoping #
Storage keys are derived via the storageToken getter ('$storagePrefix${id != null ? '_$id' : ''}').
- Singletons: Omit
id(defaults tonull). Storage key automatically uses the class name (e.g.'CounterCubit'). - Multi-Instance: Pass
idvia constructor to scope storage per user/session (CounterCubit(id: 'user_123')-> key'CounterCubit_user_123'). - Custom Keys: Override
storageTokenorstoragePrefixdirectly for custom key formats:
class CounterCubit extends HydratedCubitSignal<int> {
CounterCubit() : super(initialState: 0);
// 100% custom storage key stored in SharedPreferences
@override
String get storageToken => 'app_v2_counter_key';
}
4. Built-in Storage Adapters (SharedPreferences & FlutterSecureStorage) #
package:bloc_signals_hydrate provides pre-built, tree-shakable adapters for SharedPreferences and FlutterSecureStorage via sub-library entrypoints:
SharedPreferences
import 'package:bloc_signals_hydrate/bloc_signals_hydrate.dart';
import 'package:bloc_signals_hydrate/shared_preferences.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = await SharedPreferences.getInstance();
HydratedStorage.storage = SharedPreferencesHydratedStorage(prefs);
runApp(const MyApp());
}
FlutterSecureStorage (Keychain / KeyStore / Web Crypto)
import 'package:bloc_signals_hydrate/bloc_signals_hydrate.dart';
import 'package:bloc_signals_hydrate/secure_storage.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Pre-load secure storage map for synchronous frame 1 hydration
final secureStorage = const FlutterSecureStorage();
HydratedStorage.storage = await SecureHydratedStorage.build(secureStorage);
runApp(const MyApp());
}
⚖️ HydratedCubitSignal vs PersistentSignal (signals.dart) #
If you are evaluating state persistence approaches, both bloc_signals_hydrate and signals.dart's native PersistentSignal provide state persistence, but cater to different architectural patterns:
| Feature | HydratedCubitSignal / HydratedBlocSignal |
PersistentSignal (signals.dart) |
|---|---|---|
| Architecture Pattern | BLoC container pattern (fromJson / toJson) |
Raw key-value signal primitive |
| Hydration Timing | Synchronous during constructor execution (zero frame flicker) | Asynchronous or synchronous depending on adapter |
| Observer Telemetry | Integrated into BLoC onError / onChange observer pipeline |
Handled per-signal or via storage callbacks |
| Primitive Support | Direct primitive return (toJson(int state) => state) |
Value adapter layers |
Interoperability: Bridging PersistentSignal into BlocSignal #
If you already use PersistentSignal from package:signals, you can easily bridge it into a CubitSignal using an effect():
class CounterCubit extends CubitSignal<int> {
CounterCubit(this.persistent) : super(initialState: persistent.value) {
// Sync updates from PersistentSignal into Cubit state
effect(() => emit(persistent.value));
}
final PersistentSignal<int> persistent;
void increment() => persistent.value++;
}
🤖 AI Coding Assistant Skill #
This package is supported by an official pre-packaged AI Coding Skill representing state persistence guidelines, synchronous initial hydration semantics, and storage adapter rules for BlocSignal.
If you develop with AI coding assistants (such as Claude Code, Antigravity, Gemini, Cursor, or Codex), you can load the bloc-signals skill bundle to guide your assistant's code generation and analysis.
📜 License #
MIT License. See LICENSE for details.