Async Controller
An Async Controller is a special variable type that manages async state and cache invalidation when used with Future Builder. It prevents unnecessary retrieval when rebuilds occur and provides a way to force refresh async data when needed.
Supported Widgets
The Async Controller can be used with:
When to Use
Use an Async Controller when you need to:
Prevent Future Builder from refetching data on every rebuild
Implement manual refresh functionality (pull-to-refresh)
Force cache invalidation for async data
Control when expensive async operations should re-run
Optimize performance by caching async results
Creating an Async Controller
Navigate to Variables in your project
Click Add Variable
Select Async Controller as the type
Give it a descriptive name (e.g.,
userDataController,productsController)
How It Works
Without Async Controller
When a Future Builder rebuilds (e.g., due to state changes elsewhere on the page), it will re-execute its future/async operation every time, potentially making unnecessary API calls.
With Async Controller
When you bind an Async Controller to a Future Builder:
First build: The future executes normally
Subsequent rebuilds: The cached result is used, and the future does NOT re-execute
After
invalidate(): The cache is cleared, and the next rebuild will re-execute the future
This gives you fine-grained control over when data is fetched.
Binding to Widgets
Future Builder Example
Future Builder:
Controller: ${userDataController}
Future: API Call - fetchUserData
Builder:
Column:
Text: ${futureData.name}
Text: ${futureData.email}Controller Methods
Use the Control Object action to invoke this method on the controller.
invalidate
invalidateForces a re-evaluation or refresh of the Future Builder's data by clearing the cache.
No parameters required.
When to Use:
User pulls to refresh
User clicks a "Refresh" button
Data becomes stale and needs to be refetched
After making changes that affect the async data (e.g., updating a profile)
Example:
On Refresh Button Click:
Control Object:
Object: ${userDataController}
Method: invalidateAfter calling invalidate(), the Future Builder will re-execute its future the next time it builds.
Common Use Cases
1. Pull-to-Refresh
Implement pull-to-refresh functionality without unnecessary data fetching.
Setup:
Variable: productsController (Async Controller)
Future Builder:
Controller: ${productsController}
Future: API Call - fetchProducts
Builder:
ListView:
Data Source: ${futureData.products}Implementation:
Smart Scroll View (with Pull-to-Refresh):
On Refresh:
Control Object:
Object: ${productsController}
Method: invalidate2. Manual Refresh Button
Add a refresh button that refetches data on demand.
Setup:
Variable: dataController (Async Controller)
Future Builder:
Controller: ${dataController}
Future: API Call - fetchData
Builder:
Column:
Icon Button (Refresh):
On Click:
Control Object:
Object: ${dataController}
Method: invalidate
// Display data
ListView: ${futureData.items}3. Refresh After Mutation
Invalidate cache after updating data to ensure UI shows latest changes.
Setup:
Variable: userProfileController (Async Controller)
Future Builder:
Controller: ${userProfileController}
Future: API Call - fetchUserProfile
Builder:
Column:
Text: ${futureData.name}
Text: ${futureData.bio}
Button (Edit):
On Click:
Go To Page: EditProfilePageOn Edit Page:
On Save Button Click:
API Call: updateProfile
On Success:
Control Object:
Object: ${userProfileController}
Method: invalidate
Pop: Go back to profile page4. Preventing Unnecessary API Calls
Optimize performance by caching async results across rebuilds.
Problem Without Controller:
// Every time the page rebuilds (due to ANY state change),
// the Future Builder refetches data
Future Builder:
Future: API Call - fetchExpensiveData
Builder:
Text: ${futureData.value}
// If user clicks a button that changes unrelated state,
// the API call runs again unnecessarilySolution With Controller:
Variable: expensiveDataController (Async Controller)
Future Builder:
Controller: ${expensiveDataController}
Future: API Call - fetchExpensiveData
Builder:
Text: ${futureData.value}
// Now, rebuilds DON'T trigger the API call again
// Only when you explicitly call invalidate()5. Conditional Refresh
Invalidate only when certain conditions are met.
On Data Update:
If ${shouldRefresh}:
Control Object:
Object: ${dataController}
Method: invalidate6. Multiple Data Sources with Independent Refresh
Use separate controllers for independent data sources.
Variable: userController (Async Controller)
Variable: postsController (Async Controller)
Future Builder:
Controller: ${userController}
Future: API Call - fetchUser
Builder:
// User info
Future Builder:
Controller: ${postsController}
Future: API Call - fetchPosts
Builder:
// Posts list
// Refresh only posts
On Refresh Posts:
Control Object:
Object: ${postsController}
Method: invalidate
// Refresh only user
On Refresh User:
Control Object:
Object: ${userController}
Method: invalidateBest Practices
One controller per async data source: Create separate controllers for independent data sources
Use descriptive names: Name controllers after the data they manage (
userDataController,productsController)Invalidate after mutations: Always invalidate after updating data to keep UI in sync
Combine with loading states: Show loading indicators while data is being fetched
Handle errors: Ensure Future Builder handles error states gracefully
Don't over-invalidate: Only invalidate when data actually needs to be refreshed
Consider stale time: For data that changes rarely, avoid frequent invalidation
Common Patterns
Basic Invalidation
Control Object:
Object: ${controller}
Method: invalidateInvalidate and Show Loading
On Refresh:
Set State: isRefreshing = true
Control Object:
Object: ${dataController}
Method: invalidateInvalidate After Success
On API Success:
Control Object:
Object: ${dataController}
Method: invalidate
Toast:
Message: "Data updated!"Conditional Invalidation
If ${dataIsStale}:
Control Object:
Object: ${controller}
Method: invalidateInvalidate Multiple Controllers
On Refresh All:
Control Object:
Object: ${controller1}
Method: invalidate
Control Object:
Object: ${controller2}
Method: invalidatePerformance Benefits
Without Async Controller
User changes unrelated state
↓
Page rebuilds
↓
Future Builder re-executes
↓
Unnecessary API callWith Async Controller
User changes unrelated state
↓
Page rebuilds
↓
Future Builder uses cached data
↓
No API call (until invalidate() is called)Troubleshooting
Controller Not Working
Ensure the controller variable is properly created
Check that the controller is bound to Future Builder using
${controllerName}Verify the widget supports Async Controller
Data Not Refreshing After Invalidate
Ensure
invalidate()is being called correctlyCheck that the Future Builder is actually rebuilding after invalidation
Verify the future/API call is configured correctly
Data Still Refetching on Every Rebuild
Confirm the controller is properly bound to the Future Builder
Check that you're using the controller correctly in expressions
Related Documentation
Future Builder - Async data widget reference
Control Object Action - Invoke controller methods
API Calls - Working with async data sources
Variables Overview - Learn about all variable types
Last updated