Tools for mapping data from remote sources in Dart, similar to Elm's RemoteData: https://elmprogramming.com/remote-data.html
Package | Pub |
---|---|
remote_state |
Library inspired by a blog post by Kris Jenkins about How Elm slays a UI antipattern.
You are making an API request, and you want to display or do different things based on the status of the request.
I gained secondary inspiration from a talk by Jed Watson, A Treatise on State. As much as possible, I try to categorize state correctly in my applications.
Instead of using a complex object we use a single data type to express all possible request states. This approach makes it impossible to create invalid states.
A common use case for RemoteState would be mapping it into a UI transition or component state. Here is an example that uses StateNotifier, found in examples/counter_state_notifier
class CounterNotifier extends StateNotifier<RemoteState<int>> {
var _counterClient = CounterClient();
CounterNotifier() : super(RemoteState.initial()) {
getCount();
}
getCount() async {
state = RemoteState.loading();
state = await RemoteState.guard(() => _counterClient.getCount());
}
increment() async {
state = await RemoteState.guard(() => _counterClient.increment());
}
decrement() async {
state = await RemoteState.guard(() => _counterClient.decrement());
}
}
class ExampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: StateNotifierProvider<CounterNotifier, RemoteState<int>>.value(
value: CounterNotifier(),
child: HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
//2. Resolve counter notifier to update state
var counterNotifier = Provider.of<CounterNotifier>(context);
var counterState = Provider.of<RemoteState<int>>(context);
var textStyle = Theme.of(context).textTheme.headline4;
final fabPadding = EdgeInsets.symmetric(vertical: 5.0);
return Scaffold(
appBar: AppBar(
title: Text('RemoteState with StateNotifier'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
//3. Render state changes
counterState.when(
initial: () => Text('Not loaded', style: textStyle),
success: (value) => Text('$value', style: textStyle),
loading: () => Text('Loading...', style: textStyle),
error: (_) => Text('Error', style: textStyle),
),
],
),
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: fabPadding,
child: FloatingActionButton(
heroTag: 'inc',
child: Icon(Icons.add),
//4. Perform increment action
onPressed: () => counterNotifier.increment(),
),
),
Padding(
padding: fabPadding,
child: FloatingActionButton(
heroTag: 'dec',
child: Icon(Icons.remove),
//5. Perform decrement action
onPressed: () => counterNotifier.decrement(),
),
),
],
),
);
}
}
RemoteState<T>
is usedto annotate your request variables. It wraps all possible request states into one single union type. Use the parameters to specify.
- T: The success value type.
RemoteState.initial
is an instance of RemoteState that signifies the request hasn't been made yet.
RemoteState.loading
is an instance of RemoteState that signifies the request has been made, but it hasn't returned any data yet.
RemoteState.success
is an instance of RemoteState that signifies the request has completed successfully and the new data (of type T) is available.
RemoteState.error
is an instance of RemoteState that signifies the request has failed.
RemoteState.guard
is a static function that converts a Future to RemoteState. It will emit RemoteState.error if the future fails or RemoteState.success if the future completes.
The when
method is a high order function that accepts a method for each state and matches the request state with the appropriate callback function. All callbacks are required and must not be null.
The maybeWhen
method is a high order function that accepts a method for each state and matches the request state with the appropriate callback function or a fallback callback for missing methods. Only orElse
is required.
The map
method is the equivalent of when
without the destructuring.
The maybeWhen
method is the equivalent of when
without the destructuring.
The isInitial
predicate returns true if we haven't asked for data yet.
The isLoading
predicate returns true if we're loading.
The isSuccess
predicate returns true if we've successfully loaded some data.
The isError
predicate returns true if we've failed to load some data.