riverpie 0.2.0
riverpie: ^0.2.0 copied to clipboard
A tiny state management library for Flutter. Inspired by Riverpod.
Riverpie #
A tiny state management library for Flutter. Inspired by Riverpod.
Motivation #
After developing several production-grade apps with Riverpod, I found that it's a bit "too much" for my taste.
The code should be Flutter-like as possible, ConsumerWidget and ConsumerStatefulWidget disrupts the developer experience (e.g. refactoring doesn't work).
I want to have a simple state management library that:
- still uses
StatelessWidgetandStatefulWidgetas the main building blocks - still has type safety as in Riverpod
- has no hierarchy of providers (just access another provider with
ref.read) - exposes a simple API:
ref.watch(provider)to read, andref.notifier(provider)to write
Features #
Define a provider:
final counterProvider = NotifierProvider<Counter, int>((ref) => Counter());
class Counter extends Notifier<int> {
@override
int init() => 10;
void increment() => state++;
}
Add with Riverpie and access the provider:
class CounterPage extends StatefulWidget {
@override
State<CounterPage> createState() => _CounterState();
}
class _CounterState extends State<CounterPage> with Riverpie {
@override
Widget build(BuildContext context) {
final myCounter = ref.watch(counterProvider);
return Scaffold(
body: Column(
children: [
Text('The counter is $myCounter'),
ElevatedButton(
onPressed: () {
ref.notifier(counterProvider).increment();
// in riverpod it would be:
// ref.read(counterProvider.notifier).increment();
},
child: const Text('+ 1'),
),
],
),
);
}
}
Getting started #
Step 1: Add dependency
# pubspec.yaml
dependencies:
riverpie: <version>
Step 2: Add RiverpieScope
void main() {
runApp(
RiverpieScope(
child: const MyApp(),
),
);
}
Step 3: Define a provider
final myProvider = Provider((_) => 42);
Step 4: Use the provider
class MyPage extends StatefulWidget {
@override
State<MyPage> createState() => _CounterState();
}
class _MyPageState extends State<MyPage> with Riverpie {
@override
Widget build(BuildContext context) {
final myValue = ref.watch(myProvider);
return Scaffold(
body: Center(
child: Text('The value is $myValue'),
),
);
}
}
Stateful vs Stateless widgets #
The easiest way to use Riverpie is to use a StatefulWidget and add with Riverpie to the State class.
A StatelessWidget alone cannot be used in combination with Riverpie.
However, you can use Consumer to access the state.
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, child) {
final myValue = ref.watch(myProvider);
return Scaffold(
body: Center(
child: Text('The value is $myValue'),
),
);
},
);
}
}
Providers #
Provider
Use this provider for immutable values.
final myProvider = Provider((ref) => 42);
You may initialize this during app start:
final myProvider = Provider<PersistenceService>((_) => throw 'Not initialized');
void main() async {
final persistenceService = PersistenceService(await SharedPreferences.getInstance());
runApp(
RiverpieScope(
overrides: [
myProvider.overrideWithValue(persistenceService),
],
child: const MyApp(),
),
);
}
To access the value:
// Everywhere
int a = ref.read(myProvider);
// Inside a build method
int a = ref.watch(myProvider);
NotifierProvider
Use this provider for mutable values.
This provider can be used in an MVC-like pattern.
The notifiers are never disposed. You may have custom logic to delete values within a state.
final counterProvider = NotifierProvider<Counter, int>((ref) => Counter());
class Counter extends Notifier<int> {
@override
int init() => 10;
void increment() => state++;
}
To access the value:
// Everywhere
int a = ref.read(counterProvider);
// Inside a build method
int a = ref.watch(counterProvider);
To access the notifier:
Counter counter = ref.notifier(counterProvider);
Or within a click handler:
ElevatedButton(
onPressed: () {
ref.notifier(counterProvider).increment();
},
child: const Text('+ 1'),
)
Notifiers #
A NotifierProvider can be provided with different kinds of notifiers.
Notifier
A Notifier is the fastest and easiest way to implement a notifier.
It has access to ref, so you can use any provider at any time.
class Counter extends Notifier<int> {
@override
int init() => 10;
void increment() {
final anotherValue = ref.read(anotherProvider);
state++;
}
}
PureNotifier
A PureNotifier is the stricter option.
It has no access to ref making this notifier self-contained.
This is often used in combination with dependency injection, where you provide the dependencies via constructor.
final counterProvider = NotifierProvider<PureCounter, int>((ref) {
final persistenceService = ref.read(persistenceProvider);
return PureCounter(persistenceService);
});
class PureCounter extends PureNotifier<int> {
final PersistenceService _persistenceService;
PureCounter(this._persistenceService);
@override
int init() => 10;
void increment() {
counter++;
_persistenceService.persist();
}
}
Using ref #
With ref, you can access the providers and notifiers.
ref.read
Read the value of a provider.
int a = ref.read(myProvider);
ref.watch
Read the value of a provider and rebuild the widget when the value changes.
This should be used within a build method.
build(BuildContext context) {
final currentValue = ref.watch(myProvider);
// ...
}
ref.listen
Similar to ref.watch, but also provides a callback.
Do NOT use ref.watch and ref.listen at the same time.
build(BuildContext context) {
final currentValue = ref.listen(myProvider, (prev, next) {
print('The value changed from $prev to $next');
});
// The following is invalid, calling this before `ref.listen` even disables the callback
final currentValueWithoutCallback = ref.watch(myProvider);
// ...
}
ref.notifier
Get the notifier of a provider.
Counter counter = ref.notifier(counterProvider);
// or
ref.notifier(counterProvider).increment();
License #
MIT License
Copyright (c) 2023 Tien Do Nam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.