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)loading: Reactive boolean indicating if operation is in progressrefresh: Function to manually trigger the async operation
Example with watch function:
@override
Widget Function(BuildContext) setup() {
final userId = ref(1);
final (status, loading, refresh) = useAsyncData<User, int>(
(id) => api.fetchUser(id), // Receives userId
watch: () => userId.value, // Re-executes when userId changes
);
return (context) => Column(
children: [
if (loading.value)
const CircularProgressIndicator()
else if (status.value case AsyncData(:final value))
Text('User: ${value.name}'),
TextField(
onChanged: (value) => userId.value = int.parse(value),
),
],
);
}
Example without watch (executes once on mount):
@override
Widget Function(BuildContext) setup() {
final (status, loading, 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, loading, refresh) = useAsyncData<List<Item>, void>(
(_) => api.fetchItems(),
);
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,
}) {
final statusRef = ref<AsyncValue<T>>(const AsyncValue.idle());
Future<void> refresh() async {
if (statusRef.value.isLoading) return; // Prevent concurrent executions
statusRef.value = const AsyncValue.loading();
final watchValue = watch != null ? watch() : null as W;
await future(watchValue).then(
(result) {
statusRef.value = AsyncValue.data(result);
},
onError: (Object error, StackTrace stackTrace) {
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
// Use watch API to track value changes and trigger refresh
fw.watch(watchFn, (newVal, oldVal) {
// Only refresh if value actually changed
// This prevents infinite loops when watch source is re-evaluated
if (newVal != oldVal) {
// Don't await here - refresh updates statusRef asynchronously
unawaited(refresh());
}
});
// Execute once on mount for initial load
onMounted(refresh);
} else {
// Execute once on mount if no watch function provided
onMounted(refresh);
}
return (statusRef, refresh);
}