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

  1. Navigate to Variables in your project

  2. Click Add Variable

  3. Select Async Controller as the type

  4. 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

Forces 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: invalidate

After 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: invalidate

2. 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: EditProfilePage

On Edit Page:

On Save Button Click:
  API Call: updateProfile
    On Success:
      Control Object:
        Object: ${userProfileController}
        Method: invalidate
      
      Pop: Go back to profile page

4. 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 unnecessarily

Solution 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: invalidate

6. 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: invalidate

Best 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: invalidate

Invalidate and Show Loading

On Refresh:
  Set State: isRefreshing = true
  
  Control Object:
    Object: ${dataController}
    Method: invalidate

Invalidate After Success

On API Success:
  Control Object:
    Object: ${dataController}
    Method: invalidate
  
  Toast:
    Message: "Data updated!"

Conditional Invalidation

If ${dataIsStale}:
  Control Object:
    Object: ${controller}
    Method: invalidate

Invalidate Multiple Controllers

On Refresh All:
  Control Object:
    Object: ${controller1}
    Method: invalidate
  
  Control Object:
    Object: ${controller2}
    Method: invalidate

Performance Benefits

Without Async Controller

User changes unrelated state

Page rebuilds

Future Builder re-executes

Unnecessary API call

With 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 correctly

  • Check 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

Last updated