Migration

Migrating from 1.x

A mechanical guide to the three breaking changes in Shard 2.0.
Most apps only touch (1) if they hand-write an AsyncValue switch, and (2) if they implement a custom StateStorage. (3) requires no code change — your persisted data is read as before.

1. AsyncValue has a new AsyncIdle state

AsyncValue now has a fourth state, AsyncIdle, in addition to loading, data, and error. Any exhaustive switch you wrote over an AsyncValue is now non-exhaustive and will no longer compile.

// Before (no longer compiles — non-exhaustive):
final label = switch (state) {
  AsyncLoading() => 'Loading…',
  AsyncData(:final data) => 'Got $data',
  AsyncError(:final error) => 'Error: $error',
};

You have two ways to fix it.

// After — option A (add the missing case):
final label = switch (state) {
  AsyncIdle() => 'Idle',
  AsyncLoading() => 'Loading…',
  AsyncData(:final data) => 'Got $data',
  AsyncError(:final error) => 'Error: $error',
};
// After — option B (recommended):
final label = state.when(
  idle: () => 'Idle',
  loading: (previousData) => 'Loading…',
  data: (data) => 'Got $data',
  error: (error, stackTrace, previousData) => 'Error: $error',
);
AsyncShardBuilder users are unaffected unless they want a distinct idle UI via onIdle:. And FutureShard/StreamShard never enter the idle state, so a switch over their value only needs AsyncIdle for completeness.

2. StateStorage requires a delete method

The StateStorage interface now declares Future<void> delete(String key). Any class that implements StateStorage must add it, or it won't compile.

// Before:
class SharedPreferencesStorage implements StateStorage {
  @override
  Future<void> save(String key, String value) => _prefs.setString(key, value);
  @override
  Future<String?> load(String key) async => _prefs.getString(key);
}
// After — add delete:
class SharedPreferencesStorage implements StateStorage {
  @override
  Future<void> save(String key, String value) => _prefs.setString(key, value);
  @override
  Future<String?> load(String key) async => _prefs.getString(key);
  @override
  Future<void> delete(String key) => _prefs.remove(key);
}

For other backends, delegate to the backend's own delete:

  • Hivebox.delete(key)
  • SQLite → a DELETE statement keyed on key
  • A file store → delete the file for that key

Contract: after delete(key), a subsequent load(key) returns null, and deleting an absent key is a no-op.

3. Persisted state now uses a version envelope

Persisted values are now wrapped in a small envelope: {"__shard_v":N,"__shard_p":"…"}, where __shard_v is the schema version and __shard_p is your serialized payload.

No code change is required, and your existing data is safe. Reads are legacy-tolerant: 1.x data was written as bare payload, so it is read as version 1, and then rewritten in envelope form on the next save. Upgrading users keep everything.

A value written by 2.0 cannot be read by a 1.x build (a downgrade). If you ship a rollback, account for this.

If your persisted model changes shape over time, you can opt into schema migration by setting a version and providing a migrate callback that upgrades older payloads to the current shape. See Persistent Shard.

Nothing else changed

That's the entire breaking surface. Everything else in 2.0 is additive—new shards, mixins, widgets, and persistence options that you can adopt at your own pace. For the full tour, see What's New.

Next Steps

Next: head back to Core Concepts to put the new APIs to work, or revisit the Introduction.