useAsyncData<T, W> function
(ReadonlyRef<AsyncValue<T> > , void Function())
useAsyncData<T, W>(
- Future<
T> future(- W watchValue
- W watch()?,
Creates a reactive async operation that re-executes when watch function changes.
Unlike useFuture, this composable:
- Tracks changes in the watch function
- Automatically re-executes when watch function returns different value
- Passes watch value to the future function (if watch provided)
- Provides manual
refreshfunction for triggering - Returns detailed status and loading state
Type Parameters:
T: The type of data returned by the futureW: The type of value returned by the watch function (defaults to void)
Parameters:
future: The async function to execute. Receives watch value if watch function is providedwatch: Optional function that returns a value to watch. When the returned value changes, the future is automatically re-executed with the new value
Returns a tuple of:
status: Reactive AsyncValue with full state (idle/loading/data/error)refresh: Function to manually trigger the async operation
Derive data / error / loading refs from status with computed
when needed, or use useAsyncState instead if you don't need the raw
AsyncValue for pattern matching.
Concurrency: if refresh is called (or the watched value changes)
while a previous request is still in flight, a new request is started and
the previous request's result is discarded (latest-wins).
Example with watch function:
@override
Widget Function(BuildContext) setup() {
final userId = ref(1);
final (status, refresh) = useAsyncData<User, int>(
(id) => api.fetchUser(id), // Receives userId
watch: () => userId.value, // Re-executes when userId changes
);
final data = computed(() => status.value.dataOrNull);
final loading = computed(() => status.value.isLoading);
return (context) => Column(
children: [
if (loading.value)
const CircularProgressIndicator()
else if (data.value != null)
Text('User: ${data.value!.name}'),
TextField(
onChanged: (value) => userId.value = int.parse(value),
),
],
);
}
Example without watch (executes once on mount):
@override
Widget Function(BuildContext) setup() {
final (status, refresh) = useAsyncData<String, void>(
(_) => fetchData(),
);
return (context) {
return switch (status.value) {
AsyncLoading() => const CircularProgressIndicator(),
AsyncError(:final errorValue) => Text('Error: $errorValue'),
AsyncData(:final value) => Text('Data: $value'),
AsyncIdle() => ElevatedButton(
onPressed: refresh,
child: const Text('Load'),
),
};
};
}
Example with manual refresh:
@override
Widget Function(BuildContext) setup() {
final (status, refresh) = useAsyncData<List<Item>, void>(
(_) => api.fetchItems(),
);
final loading = computed(() => status.value.isLoading);
return (context) => Column(
children: [
if (status.value case AsyncData(:final value))
...value.map((item) => ListTile(title: Text(item.name))),
ElevatedButton(
onPressed: loading.value ? null : refresh,
child: const Text('Refresh'),
),
],
);
}
Implementation
(ReadonlyRef<AsyncValue<T>> status, void Function() refresh) useAsyncData<T, W>(
Future<T> Function(W watchValue) future, {
W Function()? watch,
}) {
assert(
watch != null || null is W,
'useAsyncData: when no `watch` function is provided, the watch type `W` '
'must be `void` or nullable, e.g. useAsyncData<T, void>((_) => ...).',
);
final statusRef = ref<AsyncValue<T>>(const AsyncValue.idle());
// Monotonically increasing request id. A newer request supersedes older
// in-flight ones: stale completions are dropped instead of new requests.
var requestId = 0;
Future<void> refresh() async {
final id = ++requestId;
statusRef.value = const AsyncValue.loading();
final watchValue = (watch != null ? watch() : null) as W;
try {
final result = await future(watchValue);
if (id == requestId) {
statusRef.value = AsyncValue.data(result);
}
} on Object catch (error, stackTrace) {
if (id == requestId) {
statusRef.value = AsyncValue.error(error, stackTrace);
}
}
}
// Watch the function and re-execute when it changes
if (watch != null) {
final watchFn = watch; // Capture to avoid shadowing
fw.watch(watchFn, (newVal, oldVal) {
// Only refresh if value actually changed
if (newVal != oldVal) {
unawaited(refresh());
}
});
}
// Execute once on mount for initial load
onMounted(refresh);
return (statusRef, refresh);
}