reactive_notifier_hydrated 2.16.1 copy "reactive_notifier_hydrated: ^2.16.1" to clipboard
reactive_notifier_hydrated: ^2.16.1 copied to clipboard

Hydrated extension for ReactiveNotifier - Automatic state persistence with customizable storage backends. Persist and restore your application state seamlessly.

reactive_notifier_hydrated #

Hydrated extension for ReactiveNotifier - Automatic state persistence with customizable storage backends.

Features #

  • Automatic state persistence and restoration
  • Support for ReactiveNotifier, ViewModel, and AsyncViewModelImpl
  • Customizable storage backends (SharedPreferences by default)
  • Version migration support for schema changes
  • Debounced persistence to optimize writes
  • Stale-while-revalidate pattern for async data

Getting Started #

1. Add dependency #

dependencies:
  reactive_notifier_hydrated: ^2.16.1

2. Initialize storage #

Initialize the storage before using any hydrated components:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize hydrated storage
  HydratedNotifier.storage = await SharedPreferencesStorage.getInstance();

  runApp(MyApp());
}

Usage #

HydratedReactiveNotifier #

For simple state that needs persistence:

// Define your state model
class CounterState {
  final int count;
  final String label;

  CounterState({required this.count, required this.label});

  Map<String, dynamic> toJson() => {'count': count, 'label': label};

  factory CounterState.fromJson(Map<String, dynamic> json) => CounterState(
    count: json['count'] as int,
    label: json['label'] as String,
  );

  CounterState copyWith({int? count, String? label}) => CounterState(
    count: count ?? this.count,
    label: label ?? this.label,
  );
}

// Create the hydrated notifier in a mixin
mixin CounterService {
  static final counter = HydratedReactiveNotifier<CounterState>(
    create: () => CounterState(count: 0, label: 'Initial'),
    storageKey: 'counter_state',
    toJson: (state) => state.toJson(),
    fromJson: (json) => CounterState.fromJson(json),
  );

  static void increment() {
    counter.transformState((state) => state.copyWith(
      count: state.count + 1,
      label: 'Count: ${state.count + 1}',
    ));
  }
}

// Use in UI
ReactiveBuilder<CounterState>(
  notifier: CounterService.counter,
  build: (state, notifier, keep) => Text('${state.count}'),
)

HydratedViewModel #

For complex state with business logic:

class SettingsViewModel extends HydratedViewModel<SettingsState> {
  SettingsViewModel() : super(
    initialState: SettingsState.defaults(),
    storageKey: 'settings',
    toJson: (state) => state.toJson(),
    fromJson: (json) => SettingsState.fromJson(json),
  );

  @override
  void init() {
    // Called once during initialization
  }

  void toggleDarkMode() {
    transformState((state) => state.copyWith(
      isDarkMode: !state.isDarkMode,
    ));
  }

  void setLanguage(String language) {
    transformState((state) => state.copyWith(
      language: language,
    ));
  }
}

// In a service mixin
mixin SettingsService {
  static final settings = ReactiveNotifier<SettingsViewModel>(
    () => SettingsViewModel(),
  );
}

HydratedAsyncViewModelImpl #

For async data with automatic caching (stale-while-revalidate pattern):

class UserViewModel extends HydratedAsyncViewModelImpl<UserModel> {
  UserViewModel() : super(
    AsyncState.initial(),
    storageKey: 'user_data',
    toJson: (user) => user.toJson(),
    fromJson: (json) => UserModel.fromJson(json),
    loadOnInit: true,  // Automatically refresh data
  );

  @override
  Future<UserModel> init() async {
    // Fetch fresh data from API
    // Cached data is shown immediately while this loads
    return await userRepository.fetchCurrentUser();
  }
}

// Usage with ReactiveAsyncBuilder
ReactiveAsyncBuilder<UserViewModel, UserModel>(
  notifier: UserService.userState.notifier,
  onData: (user, viewModel, keep) => Text(user.name),
  onLoading: () => CircularProgressIndicator(),
  onError: (error, stack) => Text('Error: $error'),
)

Version Migration #

Handle schema changes between app versions:

HydratedReactiveNotifier<UserState>(
  create: () => UserState.initial(),
  storageKey: 'user_state',
  toJson: (state) => state.toJson(),
  fromJson: (json) => UserState.fromJson(json),
  version: 2,  // Current version
  migrate: (oldVersion, oldJson) {
    if (oldVersion == 1) {
      // Migrate from v1 to v2
      return {
        ...oldJson,
        'newField': 'default_value',
        'renamedField': oldJson['oldFieldName'],
      };
    }
    return oldJson;
  },
);

Custom Storage #

Implement HydratedStorage for custom backends:

class HiveStorage implements HydratedStorage {
  final Box _box;

  HiveStorage(this._box);

  @override
  Future<Map<String, dynamic>?> read(String key) async {
    final data = _box.get(key);
    return data != null ? Map<String, dynamic>.from(data) : null;
  }

  @override
  Future<void> write(String key, Map<String, dynamic> value) async {
    await _box.put(key, value);
  }

  @override
  Future<void> delete(String key) async {
    await _box.delete(key);
  }

  @override
  Future<void> clear() async {
    await _box.clear();
  }
}

// Use custom storage
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Hive.initFlutter();
  final box = await Hive.openBox('hydrated_state');

  HydratedNotifier.storage = HiveStorage(box);

  runApp(MyApp());
}

API Reference #

HydratedReactiveNotifier #

Property/Method Description
isHydrated Whether hydration from storage is complete
hydrationComplete Future that completes when hydration is done
persistNow() Force immediate persistence (bypasses debounce)
clearPersistedState() Clear persisted data from storage
reset() Clear storage and recreate with factory default

HydratedViewModel #

Inherits all methods from ViewModel plus:

Property/Method Description
isHydrated Whether hydration from storage is complete
hydrationComplete Future that completes when hydration is done
persistNow() Force immediate persistence
clearPersistedState() Clear persisted data
reset() Clear storage and reset to clean state

HydratedAsyncViewModelImpl #

Inherits all methods from AsyncViewModelImpl plus:

Property/Method Description
isHydrated Whether hydration from storage is complete
hydrationComplete Future that completes when hydration is done
persistNow() Force immediate persistence of current data
clearPersistedState() Clear persisted data
reset() Clear storage and reload from source

Error Handling #

Handle hydration errors with callbacks:

HydratedReactiveNotifier<MyState>(
  create: () => MyState.initial(),
  storageKey: 'my_state',
  toJson: (state) => state.toJson(),
  fromJson: (json) => MyState.fromJson(json),
  onHydrationError: (error, stackTrace) {
    // Log error, use factory default
    debugPrint('Hydration failed: $error');
  },
);

Testing #

Use MockHydratedStorage in tests:

class MockHydratedStorage implements HydratedStorage {
  final Map<String, Map<String, dynamic>> _data = {};

  @override
  Future<Map<String, dynamic>?> read(String key) async => _data[key];

  @override
  Future<void> write(String key, Map<String, dynamic> value) async {
    _data[key] = value;
  }

  @override
  Future<void> delete(String key) async => _data.remove(key);

  @override
  Future<void> clear() async => _data.clear();
}

// In tests
setUp(() {
  HydratedNotifier.storage = MockHydratedStorage();
});

tearDown(() {
  HydratedNotifier.resetStorage();
});

Documentation #

For comprehensive documentation, see the docs folder:


License #

MIT License - see LICENSE file for details.

2
likes
140
points
200
downloads

Publisher

verified publisherjhonacode.com

Weekly Downloads

Hydrated extension for ReactiveNotifier - Automatic state persistence with customizable storage backends. Persist and restore your application state seamlessly.

Repository (GitHub)
View/report issues

Topics

#state-management #persistence #hydration #reactive #cache

Documentation

API reference

License

MIT (license)

Dependencies

flutter, reactive_notifier, shared_preferences

More

Packages that depend on reactive_notifier_hydrated