Guides
Loading Data Async
This is an exploration of loading data asynchronously within Flutter Forge.
AsyncState
Flutter Forge provides a AsyncState
class to make representing state around asynchronous processes easier. You can conceptually think of it as a enum
that can only be in one of the states, initial
, loading
, data(value)
, and error(error, stackTrace)
.
This allows you to easily define your asynchronous state in terms of AsyncState
.
In the following we define state for the AsyncStateWidgetExample
. The thing of note here is the use of AsyncState<EquatableString>
as the type for the name
property in the state class.
@immutable
class AsyncStateWidgetExampleState extends Equatable {
const AsyncStateWidgetExampleState({required this.name});
final AsyncState<EquatableString> name;
@override
List<Object> get props => [name];
}
Using the AsyncState
type in this manner makes it so that the name
property has to be in one of the following states.
AsyncState.initial
AsyncState.loading
AsyncState.data(value)
AsyncState.error(error, stackTrace)
It also means that where the widget uses the name
property from the state class it has to handle all of these different cases. To aid with this the AsyncState
class provides a method called when()
that facilitates exhaustively handling each of these cases. The following is an example of what that might look like.
state.name.when(
initial: () => const Text('Initial Name'),
loading: () => const Text('Loading...'),
data: (v) => Text('data = $v'),
error: (e, __) => Text('error = ${e.toString()}'))
Updating AsyncState
You can update a AsyncState
value just as you would with any other value. Just use AsyncState.initial
, AsyncState.loading
, AsyncState.data(value)
, or AsyncState.error(error, stackTrace)
to set it to the case you want.
Set Post Init State
The ComponentWidget
provides a lifecycle method called postInitState
that can be overridden to facilitate triggering an action when the widget is first created.
@override
void postInitState(viewStore) {
viewStore.send(Load());
}
The above is a quick example of how you might do this. You would then of course have to have your reducer handle this action and fire an effect which would then send a new action into the store with its loaded data or error depending on its state.
AsyncStateWidget
Doing the loading and wiring up of the actions & effects to facilitate fetching data asynchronously can be quite cumbersome. This is unnecessary as most of it is boilerplate code. To simplify this we have provided the AsyncStateWidget
which is designed to make it a bit easier to automatically load & update AsyncState
.
To use it you simply drop this widget into your component as a child component and hand it a Store that it cares about.
AsyncStateWidget(
store: store.scopeAsyncStateSync(
loader: (_) => Future.delayed(const Duration(seconds: 5),
() => const EquatableString("Woot woot!")),
toChildState: (s) => s.name,
fromChildState: (s, cs) =>
AsyncStateWidgetExampleState(name: cs)))
In the above example you can see that we simply construct an instance of AsyncStateWidget
which acts like any other Flutter Forge component and takes a Store.
The AsyncStateWidget
has a generic state of AsyncState<T> where T extends Equatable
. With our components state we have a property called name
that matches this type. To facilitate loading AsyncState
values, like our name
property, we provide the scopeAsyncStateSync()
method on the Store.
This takes three arguments.
FutureOr<T> Function(E) loader
- a function that asynchronously loads the data. In our example thename
value. In our example we useFuture.delayed
return the string in 5 seconds.AsyncState<T> Function(S) toChildState
- a function that translates from the parent components state to the child components state. Since this childAsyncStateWidget
simply cares about loading thename
we translate from the parents state object to thename
which is aAsyncState<EquatableString>
.S Function(S, AsyncState<T>) fromChildState
- a function that translates from child state, in this case,AsyncState<T>
to parent state. In our example we simply construct the parent state with the child state as the name.
This facilitates the asynchronous loading of the data and the boilerplate code in terms of actions, reducer implementation, and effects. Beyond that, it facilitates properly syncing the state changes between the child component and the parent component.