AsyncNotifier
A ValueNotifier for all async states. Listen, notify, and manage loading, error, and data in one place.
Usage
AsyncNotifier
is essentially a ValueNotifier
with two new setters: future
and stream
. All it's states are resolved into an AsyncSnapshot
. Due to it's nature, it's easily integrated with Flutter own widgets and classes, making it simple and straightforward. Two objects we all know, working together as one.
- Here's a simple overview:
import 'package:async_notifier/async_notifier.dart';
void main() {
// No initial value needed. Defaults to AsyncSnapshot.nothing().
final counter = AsyncNotifier<int>();
// Listenable.
counter.addListener(() {
print("New state: ${counter.snapshot}");
});
// You can set a future or stream.
counter.future = Future.value(42);
counter.stream = Stream.fromIterable([1, 2, 3]);
// And get its snapshot.
final AsyncSnapshot<int> snapshot = counter.snapshot;
// Check its states with extensions:
snapshot.isLoading
snapshot.isReloading
snapshot.requireData
snapshot.hasData
snapshot.hasError
snapshot.hasNone
snapshot.error
snapshot.stackTrace
// Control its future or stream:
counter.cancel(); // works for future!
counter.dispose();
// And resolve them with ease:
final result = counter.when(
data: (data) => 'Data $data',
error: (error, stackTrace) => 'Error $error',
loading () => 'Loading',
);
// Or even:
final result = switch (snapshot) {
AsyncSnapshot(:var data?) => 'Data $data',
AsyncSnapshot(:var error?) => 'Error $error',
_ => 'Loading',
}
}
Benefits
- Simplified State Management: Resolves the Future and Stream states in the view model layer.
- Easy to Use: Just set the
future
orstream
and letAsyncNotifier
handle the rest. - Reactive: Automatically notifies listeners when the each state changes.
State Management
You can listen to all AsyncNotifier states directly and bind it to other objects.
class TodosNotifier extends ChangeNotifier {
TodosNotifier() {
_todos.addListener(notifyListeners);
fetchTodos();
}
final _todos = AsyncNotifier<List<Todo>>();
AsyncSnapshot<List<Todo>> get todosSnapshot => _todos.snapshot;
void fetchTodos() {
_todos.future = _repository.fetchTodos();
}
void dispose() {
_todos.dispose();
super.dispose();
}
}
Consuming the State
You can use Flutter native solutions like ListenableBuilder
.
class TodoList extends StatelessWidget {
const TodoList({super.key, required this.todosNotifier});
final TodosNotifier todosNotifier;
@override
Widget build(BuildContext context) {
// This Flutter builder rebuilds whenever our AsyncNotifier changes.
return ListenableBuilder(
listenable: todosNotifier,
builder: (context, _) {
// Use `when` for resolving AsyncNotifier states.
return todosNotifier.snapshot.when(
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error: $error'),
data: (data) => ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) => Text(data[index].title),
),
);
},
);
}
}
With provider
:
class TodoList extends StatelessWidget {
const TodoList({super.key});
@override
Widget build(BuildContext context) {
final todosSnapshot = context.watch<TodosNotifier>().snapshot;
// Use `when` for resolving AsyncSnapshot states.
return todosSnapshot.when(
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error: $error'),
data: (data) => ListView.builder(
itemCount: todosListenable.value.length,
itemBuilder: (context, index) => Text(data[index].title),
),
);
}
}
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Libraries
- async_notifier
- A ValueNotifier extension for all your async states. Listen, notify, and manage loading, error and data in one place.