Data
AsyncData<T> represents the successful state of an AsyncValue, containing the value produced by a completed asynchronous operation.
What is it?
AsyncData<T> is one of the three possible states of an AsyncValue.
It indicates that an asynchronous operation has completed successfully and the requested value is now available.
For example:
- An API request returned data.
- A database query completed successfully.
- A file was loaded.
- A stream emitted a new value.
Unlike AsyncLoading or AsyncError, AsyncData always contains a valid value of type T.
Why does it exist?
After an asynchronous operation finishes successfully, your application needs a safe and consistent way to access the result.
Instead of manually checking:
- Is the request finished?
- Did an error occur?
- Is the value null?
Riverpod wraps the successful result inside AsyncData, making it easy to build the UI.
This provides:
- Type safety
- Predictable state handling
- Consistent APIs across all asynchronous providers
How Data Works
When an asynchronous operation succeeds, Riverpod transitions from AsyncLoading to AsyncData.
Request Starts
│
▼
AsyncLoading
│
▼
Request Succeeds
│
▼
AsyncData
The value is now available to the application.
Syntax
FutureProvider
final userProvider = FutureProvider<User>((ref) async {
return repository.fetchUser();
});
Explanation:
- Once
fetchUser()completes successfully, Riverpod creates anAsyncData<User>.
Reading Data with when()
final user = ref.watch(userProvider);
return user.when(
loading: () => const CircularProgressIndicator(),
data: (user) => Text(user.name),
error: (error, stackTrace) {
return Text(error.toString());
},
);
Explanation:
- The
datacallback receives the successful value. - No additional casting or null checks are required.
Accessing value
if (user.hasValue) {
print(user.value);
}
Explanation:
hasValueindicates whether data is available.valuereturns the wrapped data.
Data Lifecycle
AsyncLoading
│
▼
AsyncData
│
▼
UI Displays Data
The provider remains in the data state until another refresh or update occurs.
Mental Model
Think of AsyncData as a delivered package.
Order Placed
│
▼
Shipping
│
▼
Delivered
Once the package arrives:
- The waiting is over.
- No error occurred.
- The requested item is available.
AsyncData represents that delivered result.
Examples
Display User Information
user.when(
loading: () => const CircularProgressIndicator(),
data: (user) {
return Text(user.name);
},
error: (error, stackTrace) {
return Text(error.toString());
},
);
Explanation:
- Displays the user's name after a successful request.
Display Product List
products.when(
loading: LoadingView.new,
data: ProductGrid.new,
error: ErrorView.new,
);
Explanation:
- Builds the product grid only when products are available.
Using hasValue
if (user.hasValue) {
return Text(user.value!.name);
}
Explanation:
- Safely checks whether data exists before accessing it.
- Useful for simple conditional rendering.
Real-World Example
final profile = ref.watch(profileProvider);
return profile.when(
loading: ProfileSkeleton.new,
data: ProfileView.new,
error: ErrorScreen.new,
);
Explanation:
- The profile screen is shown only after successful loading.
When to Use
Use the data state when:
- Displaying API responses.
- Showing database records.
- Rendering stream values.
- Building successful asynchronous UIs.
- Working with completed async operations.
When NOT to Use
Avoid accessing data directly when:
- The provider is still loading.
- The provider has failed.
- The value hasn't been verified.
Instead:
- Use
when(). - Check
hasValue. - Handle errors appropriately.
Best Practices
- Prefer
when()for handling data. - Treat
AsyncDataas immutable. - Display meaningful content after successful loading.
- Keep UI logic separate from business logic.
- Avoid force-unwrapping values without checking state.
Common Mistakes
1. Force-Unwrapping value
❌ Wrong
print(user.value!.name);
Why it's wrong:
- The provider may still be loading or in an error state.
✔ Correct
user.when(
loading: () => const CircularProgressIndicator(),
data: (user) => Text(user.name),
error: (error, stackTrace) {
return Text(error.toString());
},
);
2. Ignoring Error State
❌ Wrong
if (user.hasValue) {
return Text(user.value!.name);
}
return const SizedBox();
Why it's wrong:
- Errors are silently ignored.
- Users receive no feedback.
✔ Correct
Always handle loading, data, and error states.
3. Using Data Without Checking
❌ Wrong
final user = ref.watch(userProvider);
Text(user.value!.name);
Why it's wrong:
valuemay be unavailable.
✔ Correct
Use when() or verify hasValue.
4. Storing AsyncData Separately
❌ Wrong
AsyncData<User> currentUser;
Why it's wrong:
- Applications should typically work with
AsyncValue<User>. - Riverpod manages state transitions automatically.
✔ Correct
Use:
AsyncValue<User>
and handle all possible states.
AsyncData vs AsyncLoading vs AsyncError
| State | Meaning | Contains Value |
|---|---|---|
AsyncLoading |
Waiting for completion | ❌ |
AsyncData |
Operation succeeded | ✅ |
AsyncError |
Operation failed | ❌ |
Related APIs
AsyncValueAsyncLoadingAsyncErrorwhen()maybeWhen()map()FutureProviderStreamProviderAsyncNotifier
Summary
AsyncData<T> represents the successful completion of an asynchronous operation. It safely wraps the resulting value and provides a consistent way to build UI after data has been loaded. By using AsyncData through APIs like when(), Riverpod applications can display asynchronous results in a predictable, type-safe manner while keeping loading and error handling separate.