async_state_view 0.1.0 copy "async_state_view: ^0.1.0" to clipboard
async_state_view: ^0.1.0 copied to clipboard

Render retained asynchronous state without owning or restarting the work.

async_state_view #

Render controller-owned asynchronous state without giving a widget ownership of the Future.

AsyncStateBuilder is a small view layer for retained async state. Your controller, notifier, or State object decides when work starts, which result wins, and how long the result lives. The widget only renders AsyncLoading, AsyncData, or AsyncError.

Why use it? #

Future-driven builders are convenient when one Future belongs to one widget. They become less natural when the result belongs to a feature or controller. AsyncStateBuilder keeps that ownership boundary explicit.

It works especially well when:

  • Several widgets render different parts of the same result.
  • An action must refresh data after changing something.
  • Data must survive individual widget rebuilds or moves.
  • A controller must reject stale results or ignore completions after disposal.
  • Tests need to publish loading, data, and error states without completing real Futures.
  • You want an exhaustive, non-nullable sealed state instead of inspecting an AsyncSnapshot.

The package is deliberately small. It depends only on Flutter, does not impose a state-management framework, and works with setState, ChangeNotifier, Bloc, Riverpod, or a custom controller.

Installation #

Add the package to your application:

flutter pub add async_state_view

Then import its public library:

import 'package:async_state_view/async_state_view.dart';

How it compares #

Option Who owns the asynchronous work? Best fit
AsyncStateBuilder Your state owner Controller-owned state shared across UI or refreshed by feature actions
FutureBuilder The widget subscribes to a Future One widget displaying one already-created Future
async_builder The widget subscribes to a Future or Stream A convenient FutureBuilder/StreamBuilder with separate callbacks and stream features
flutter_async_value Your state owner The closest state-first alternative, with initial state, typed errors, mapping helpers, and an AsyncValueBuilder
Riverpod with AsyncValue A provider App state needing dependency tracking, caching, lifecycle management, and provider composition
flutter_async The package/widget Turnkey reload, polling, pagination, async buttons, and global loading or error presentation

Compared with FutureBuilder #

Flutter's built-in FutureBuilder subscribes to a Future and exposes an AsyncSnapshot. It is usually the best choice for a small, local UI concern. The Future must be created before build; creating it alongside the builder can restart the task whenever an ancestor rebuilds.

Choose AsyncStateBuilder when the result has a lifecycle beyond that widget. The state owner decides exactly when loading begins, whether an old completion is stale, and when to refresh. The builder simply renders the current state.

Compared with async_builder #

The async_builder package is a higher-level replacement for FutureBuilder and StreamBuilder. It directly accepts a Future or Stream and provides separate callbacks for waiting, values, errors, and closed streams. It also has options for initial values, retaining a value, pausing streams, reporting errors, and initializing work with InitBuilder.

Choose async_builder when the widget should own the subscription and you want those conveniences with less snapshot boilerplate. Choose AsyncStateBuilder when a controller already owns the work and the widget should not subscribe to, restart, pause, or otherwise manage it.

Is state-first async handling unusual? #

No. flutter_async_value has a very similar split between an AsyncValue and an AsyncValueBuilder. Riverpod also exposes async work as a retained, sealed AsyncValue that widgets consume. This package applies the same idea without adopting a full state-management system.

Many packages accept a Future directly because that is the shortest path from the value most applications already have to visible UI. The widget can manage the subscription and disposal automatically, and users do not have to write a state owner. That is a good default for local work, but it couples operation lifecycle to widget lifecycle. This package chooses explicit state ownership for cases where that coupling is undesirable.

Complete example #

Most asynchronous work still starts as a Future. Await that Future in your state owner, translate its progress into an AsyncState, and rebuild when the state changes.

This example loads a user when the widget starts and lets the user retry after an error:

import 'dart:async';

import 'package:async_state_view/async_state_view.dart';
import 'package:flutter/material.dart';

class UserPage extends StatefulWidget {
  const UserPage({super.key});

  @override
  State<UserPage> createState() => _UserPageState();
}

class _UserPageState extends State<UserPage> {
  AsyncState<User> _user = const AsyncLoading();
  var _requestGeneration = 0;

  @override
  void initState() {
    super.initState();
    unawaited(_loadUser());
  }

  Future<void> _loadUser() async {
    final generation = ++_requestGeneration;
    setState(() => _user = const AsyncLoading());

    try {
      // This is the Future your repository, API client, or service returns.
      final user = await userRepository.fetchCurrentUser();

      // Ignore a result if the widget was removed or a newer load was started.
      if (!mounted || generation != _requestGeneration) return;
      setState(() => _user = AsyncData(user));
    } catch (error, stackTrace) {
      if (!mounted || generation != _requestGeneration) return;
      setState(() => _user = AsyncError(error, stackTrace));
    }
  }

  @override
  Widget build(BuildContext context) {
    return AsyncStateBuilder<User>(
      state: _user,
      loading: (_) => const Center(child: CircularProgressIndicator()),
      data: (_, user) => Text('Hello, ${user.name}'),
      error: (context, error, stackTrace) => Column(
        children: [
          Text(
            'Could not load the user: $error',
            style: TextStyle(color: Theme.of(context).colorScheme.error),
          ),
          ElevatedButton(
            onPressed: _loadUser,
            child: const Text('Retry'),
          ),
        ],
      ),
    );
  }
}

User and userRepository represent your own model and data source.

Updating the state #

Publish the state that matches the current outcome and notify the UI using your existing state-management mechanism:

  • Before starting or restarting work: const AsyncLoading()
  • After the Future succeeds: AsyncData(value)
  • After the Future fails: AsyncError(error, stackTrace)

The example uses setState, but the same states can be stored in a ChangeNotifier, Bloc, Riverpod notifier, or another controller. Pass the current AsyncState<T> to AsyncStateBuilder after that owner rebuilds its listeners.

Avoid creating or starting the Future inside build. Start it from a lifecycle method, event handler, or controller instead.

When to use something else #

Use FutureBuilder when the operation is local to one widget and Flutter's built-in API is sufficient. Adding a retained state model would only create extra code.

Use async_builder or another Future/Stream builder when you want the widget to subscribe directly, especially for streams. AsyncStateBuilder has no Stream support and does not listen for changes by itself.

Use flutter_async_value if you want this state-first approach plus an idle state, typed errors, map/maybeMap, or result helpers.

Use Riverpod, Bloc, or another state-management solution when you also need dependency injection, automatic disposal, caching, cross-feature coordination, side-effect listeners, or developer tooling. This package can render state owned by those tools, but it does not replace them.

Consider a more feature-rich async UI package such as flutter_async when you need built-in reload, polling, pagination, async-button behavior, snackbars, or globally configured loaders.

AsyncState models exactly three mutually exclusive states: loading, data, and error. Use another model or extend your feature state when you need an idle state, progress, pagination, cancellation, cached data while refreshing, or simultaneous data-and-error information.

Regardless of the UI package, cancellation and stale-result handling belong to the code that owns the operation. AsyncStateBuilder neither starts nor cancels work.

1
likes
160
points
76
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Render retained asynchronous state without owning or restarting the work.

Repository (GitHub)
View/report issues

Topics

#async-state #flutter-widget #state-management

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on async_state_view