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 the name value. In our example we use Future.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 child AsyncStateWidget simply cares about loading the name we translate from the parents state object to the name which is a AsyncState<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.

Previous
Composing Components