Handling Asynchronous Data in Flutter with Generic Classes
An implementation that defines data states using a generic class and returns widgets based on the current state.
1. Initial Data Declaration
Ds<Profile> profileInfo = Loading(); // or Loading<Profile>();
2. Fetching Data
Future<void> fetchData() async {
try {
profileInfo = Fetched(
User(
imgUrl: 'https://avatars.githubusercontent.com/u/75591730?v=4',
name: 'Ximya',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing ...',
),
);
log('Data successfully fetched');
} catch (e) {
profileInfo = Failed(e);
log('Data fetching failed. ${e}');
}
}
3. Returning Widgets Based on States
final profile = controller.profileInfo;
return profile.onState(
fetched: (value) => ProfileCard(value),
failed: (e) => ErrorIndicator(e),
loading: () => CircularProgressIndicator(),
);
Basic Module
sealed class Ds<T> {
Ds({required this.state, this.error, this.valueOrNull});
T? valueOrNull;
Object? error;
DataState state;
T get value => valueOrNull!;
R onState<R>({
required R Function(T data) fetched,
required R Function(Object error) failed,
required R Function() loading,
}) {
if (state.isFailed) {
return failed(error!);
} else if (state.isLoading) {
return loading();
} else {
return fetched(valueOrNull as T);
}
}
}
class Fetched<T> extends Ds<T> {
final T data;
Fetched(this.data) : super(state: DataState.fetched, valueOrNull: data);
}
class Loading<T> extends Ds<T> {
Loading() : super(state: DataState.loading);
}
class Failed<T> extends Ds<T> {
final Object error;
Failed(this.error) : super(state: DataState.failed, error: error);
}
enum DataState {
fetched,
loading,
failed;
bool get isFetched => this == DataState.fetched;
bool get isLoading => this == DataState.loading;
bool get isFailed => this == DataState.failed;
}