This package has been integrated (and improved) into Flutter Data
Easily produce and consume loading/error/data states in your application.
DataState
is a StateNotifier
-based alternative to AsyncSnapshot
.
- Produce events from
DataStateNotifier
,Future
,Stream
, RxDartValueStream
- Consume events through
DataStateNotifier
,Stream
Flutter example:
(Note: This example depends on flutter_data_state which is a separate package)
@override
Widget build(BuildContext context) {
return DataStateBuilder<List<Post>>(
notifier: () => repo.watchPosts(),
builder: (context, state, notifier, _) {
return Column(
children: [
if (state.isLoading)
CircularProgressIndicator(),
if (state.hasException)
ExceptionWidget(state.exception),
if (state.hasModel)
ShowPost(state.model),
],
);
}
);
}
The notifier features a reload
function, useful to restart a data loading cycle. It can be combined with a gesture detector or reloader widget:
Example 1:
GestureDetector(
onTap: () => notifier.reload(), // will trigger a rebuild with isLoading = true
child: _child,
)
Example 2:
RefreshIndicator(
onRefresh: notifier.reload,
child: _child,
),
This package exposes an extension on Future<T> Function()
called asDataNotifier
.
It allows to turn any future into a DataStateNotifier
callback to leverage caching and reloading capabilities.
Example 1:
final notifier = (() => future).asDataNotifier;
Example 2:
final notifier = (() {
if (Random().nextInt(10) > 4) {
return Future<String>.error('Error!');
}
return Future.delayed(
Duration(seconds: 1),
() => 'HELLO RANDOM: ${Random().nextInt(100)}!',
);
}).asDataNotifier;
Same for Stream
s and ValueStream
s:
final notifier = (() => stream).asDataNotifier;
Want to consume events via streams?
DataStateNotifier
actually exposes an RxDart ValueStream
:
@override
Widget build(BuildContext context) {
final stream = repo.watchPosts().stream;
return StreamBuilder<List<Post>>(
initial: stream.value,
stream: stream,
builder: (context, snapshot) {
// snapshot as usual
}
);
}
This is the anatomy of an immutable DataState
object:
final state = DataState({
T model,
bool isLoading = false,
Object exception,
StackTrace stackTrace,
});
Example:
DataStateNotifier<List<Post>> watchPosts() {
final notifier = DataStateNotifier<List<Post>>(
DataState(model: getLocalPosts()),
reload: (notifier) async {
notifier.state = notifier.state.copyWith(isLoading: true);
try {
notifier.data = await loadPosts();
} catch (error, stackTrace) {
notifier.data = notifier.data.copyWith(exception: error, stackTrace: stackTrace);
}
},
);
// kick off cycle
return notifier..reload();
}
The DataStateNotifier
constructor takes:
state
as the first positional argumentreload
andonError
as optional named arguments
This would allow us to do the following destructuring:
state.when(
data: (data) => Text(data),
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error $error'),
);
This turns out to be impractical in data-driven Flutter apps as there are cases where we need to render loading/error messages in addition to data – not instead of data.
No. It can used with pure Dart.
Please use Github to ask questions, open issues and send PRs. Thanks!
See LICENSE