Design principles behind miniriverpod.
The package keeps its feature set focused so behavior stays explicit: provider identity by args, scoped injection, and predictable disposal.
What Changes from Riverpod
Instead of generated family classes and implicit notifier channels, miniriverpod prefers subclass + args + explicit invoke.
Provider identity
runtimeType + args hash
family alternative
Subclass Provider / AsyncProvider and pass super.args((...))
DI fallback
Scope<T>.required + overrideWithValue
Why this matters
Plain Dart constructors make equality and overrides easy to reason about, keeping debugging and tests straightforward.
Provider Identity with args
args defines the provider key, so equal args resolve to the same cache entry inside a ProviderContainer.
Identity rule
Practical consequences
- No dedicated family type is required.
- Per-argument override is done by creating provider instances.
- Maintain args stable and immutable for predictable caching.
Example: family-like provider + Scope fallback
Choose a constructor argument as identity and inject a fallback instance through Scope. The workflow remains easy to review.
class ProductProvider extends AsyncProvider<List<Product>> {
ProductProvider({this.search = ''}) : super.args((search,));
final String search;
static final fallback = Scope<ProductProvider>.required('product.fallback');
@override
FutureOr<List<Product>> build(ref) async {
final api = ref.watch(productsApiProvider);
return api.search(q: search);
}
}
// Inject
ProviderScope(
overrides: [
ProductProvider.fallback.overrideWithValue(ProductProvider(search: 'jeans')),
],
child: const App(),
);
Next Steps
Providers & Reads
See concrete patterns for watch/read/listen and AsyncProvider.future.
Open Providers