remote_province 0.0.1 remote_province: ^0.0.1 copied to clipboard
Tools for mapping data from remote sources in Dart, similar to Elm's RemoteData.
RemoteState #
Tools for mapping data from remote sources in Dart, similar to Elm's RemoteData: https://elmprogramming.com/remote-data.html
Package | Pub |
---|---|
remote_state |
Slaying a UI Antipattern with Flutter. #
Library inspired by a blog post by Kris Jenkins about How Elm slays a UI antipattern.
What problem does this package solve? #
You are making an API request, and you want to display or do different things based on the status of the request.
Why RemoteState, not RemoteData? #
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.
The RemoteState approach #
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.
Usage #
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
counter/notifier/counter.dart #
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());
}
}
main.dart #
class ExampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: StateNotifierProvider<CounterNotifier, RemoteState<int>>.value(
value: CounterNotifier(),
child: HomePage(),
),
);
}
}
home.dart #
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(),
),
),
],
),
);
}
}
API #
RemoteState #
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 #
RemoteState.initial
is an instance of RemoteState that signifies the request hasn't been made yet.
RemoteState.loading #
RemoteState.loading
is an instance of RemoteState that signifies the request has been made, but it hasn't returned any data yet.
RemoteState.success #
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 #
RemoteState.error
is an instance of RemoteState that signifies the request has failed.
RemoteState.guard #
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.
Pattern matching high order functions #
When #
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.
MaybeWhen #
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.
Map #
The map
method is the equivalent of when
without the destructuring.
MaybeMap #
The maybeWhen
method is the equivalent of when
without the destructuring.
State Predicates #
isInitial #
The isInitial
predicate returns true if we haven't asked for data yet.
isLoading #
The isLoading
predicate returns true if we're loading.
isSuccess #
The isSuccess
predicate returns true if we've successfully loaded some data.
isError #
The isError
predicate returns true if we've failed to load some data.