flutter_api_state 0.0.6 copy "flutter_api_state: ^0.0.6" to clipboard
flutter_api_state: ^0.0.6 copied to clipboard

A Flutter package that simplifies async UI state handling (loading, success, error, empty) with built-in retry and pull-to-refresh support.

flutter_api_state #

ApiStateBuilder — a declarative Flutter widget that replaces repetitive FutureBuilder boilerplate. It handles loading, error, empty, and success states in one place, with built-in retry, pull-to-refresh, and an optional network check.

Why #

Most screens that talk to an API end up writing the same FutureBuilder block: check connectionState, check hasError, check for an empty list, then render the success UI. ApiStateBuilder collapses all four branches into one widget — and adds the things you usually bolt on later (retry, refresh, offline check).

Install #

dependencies:
  flutter_api_state: ^0.0.6
import 'package:flutter_api_state/flutter_api_state.dart';

Quick start #

Every feature wired up in one widget:

ApiStateBuilder<List<User>>(
  future: () => fetchUsers(),
  
  loading: const Center(child: CircularProgressIndicator()),
  
  error: (context, error, stack) => Center(child: Text('$error')),
  
  empty: const Center(child: Text('No users')),
  
  success: (context, users) => UserList(users),
  
  enablePullToRefresh: true,
  refreshIndicatorColor: Colors.red,
 
  enableRetry: true,
  retryButtonBuilder: (ctx, onRetry) => OutlinedButton(
    onPressed: onRetry,
    child: const Text('Try again'),
  ),

  enableNetworkCheck: true,
  noNetwork: const Center(child: Text('You are offline. Please reconnect.')),
)

Strip out the bits you don't need — only future and success are required.

future takes a factory (() => fetchUsers()), not a Future directly. That's what lets the widget re-invoke it for retry, pull-to-refresh, and network re-check.

Generic support #

ApiStateBuilder<User>(...)
ApiStateBuilder<List<User>>(...)
ApiStateBuilder<Map<String, dynamic>>(...)
ApiStateBuilder<String>(...)
ApiStateBuilder<bool>(...)

Retry button #

ApiStateBuilder<List<User>>(
  future: () => fetchUsers(),
  enableRetry: true,
  retryButtonBuilder: (ctx, onRetry) => OutlinedButton(
    onPressed: onRetry,
    child: const Text('Try again'),
  ),
  error: (ctx, e, _) => Text('$e'),
  success: (ctx, users) => UserList(users),
)

Conditions: the button only appears when all three are true — the future threw, enableRetry: true, and (optionally) retryButtonBuilder is set. Tapping it re-invokes future().

Pull-to-refresh #

ApiStateBuilder<List<User>>(
  future: () => fetchUsers(),
  enablePullToRefresh: true,
  refreshIndicatorColor: Colors.red,
  success: (ctx, users) => ListView.builder(/* ... */),
)

Works whether your success widget is already a scrollable (ListView / GridView / CustomScrollView) or a plain widget like Center — the package detects the type and wraps only when needed.

Network check #

Skip the request entirely when the device is offline:

ApiStateBuilder<List<Post>>(
  future: () => fetchPosts(),
  enableNetworkCheck: true,
  noNetwork: const Center(child: Text('You are offline. Please reconnect.')),
  enableRetry: true,   // shows a Retry button under noNetwork too
  success: (ctx, posts) => PostList(posts),
)

The default check resolves DNS for one.one.one.one with a 3 s timeout (mobile/desktop). On web the default is a no-op (true) because the browser already handles its own offline behavior.

Plug in connectivity_plus or your own logic:

ApiStateBuilder<List<Post>>(
  future: () => fetchPosts(),
  enableNetworkCheck: true,
  hasNetwork: () async {
    final result = await Connectivity().checkConnectivity();
    return result != ConnectivityResult.none;
  },
  noNetwork: const OfflineBanner(),
  success: (ctx, posts) => PostList(posts),
)

Note on captive portals: the default DNS check correctly reports offline on hotel/airport Wi-Fi where you're "connected" but blocked. If you want "any network present" semantics instead, plug in connectivity_plus as shown above.

Full example #

The example/ folder contains a runnable app that fetches posts from jsonplaceholder.typicode.com with all features turned on:

ApiStateBuilder<List<dynamic>>(
  future: () => fetchPosts(),
  loading: const Center(child: CircularProgressIndicator()),
  enablePullToRefresh: true,
  refreshIndicatorColor: Colors.red,
  enableRetry: true,
  retryButtonBuilder: (ctx, onRetry) => OutlinedButton(
    onPressed: onRetry,
    child: const Text('Try again'),
  ),
  success: (context, posts) => ListView.builder(
    itemCount: posts.length,
    itemBuilder: (context, i) => Card(
      margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      child: ListTile(
        title: Text(posts[i]['title'],
            style: const TextStyle(fontWeight: FontWeight.bold)),
        subtitle: Text(posts[i]['body']),
      ),
    ),
  ),
  error: (context, error, stack) => Center(child: Text('$error')),
  empty: const Center(child: Text('No posts')),
)

Run it locally:

cd example
flutter pub get
flutter run

Cookbook #

1. API list screen with everything turned on #

ApiStateBuilder<List<Post>>(
  future: () => api.fetchPosts(),
  loading: const Center(child: CircularProgressIndicator()),
  empty: const Center(child: Text('No posts yet')),
  error: (_, e, __) => Center(child: Text('Failed: $e')),
  success: (_, posts) => ListView(
    children: [for (final p in posts) PostTile(p)],
  ),
  enableRetry: true,
  enablePullToRefresh: true,
  enableNetworkCheck: true,
)

2. User profile API (single object) #

ApiStateBuilder<User>(
  future: () => api.fetchUser(id),
  loading: const CircularProgressIndicator(),
  success: (_, user) => ProfileView(user),
)

3. Custom empty state #

ApiStateBuilder<List<Task>>(
  future: () => api.fetchTasks(),
  empty: const _NoTasksPlaceholder(),
  success: (_, tasks) => TaskList(tasks),
)

4. Custom error widget with stack trace #

ApiStateBuilder<Report>(
  future: () => api.fetchReport(),
  error: (_, e, stack) => ErrorView(error: e, stack: stack),
  success: (_, report) => ReportView(report),
)

5. Skeleton-shimmer loading #

ApiStateBuilder<List<Item>>(
  future: () => api.fetchItems(),
  loading: const ShimmerListSkeleton(),
  success: (_, items) => ItemGrid(items),
)

Changelog #

See CHANGELOG.md.

License #

MIT — see LICENSE.

2
likes
0
points
59
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package that simplifies async UI state handling (loading, success, error, empty) with built-in retry and pull-to-refresh support.

License

unknown (license)

Dependencies

flutter

More

Packages that depend on flutter_api_state