builder_plus 1.1.8 copy "builder_plus: ^1.1.8" to clipboard
builder_plus: ^1.1.8 copied to clipboard

A Flutter package for simplified async operations with built-in loading, error, and empty states management.

Repository on GitHub | Report an Issue

builder_plus Package #

A Flutter package to simplify the management of asynchronous operations with loading, error, and empty data states.

Features #

  • FutureWorker: Simplified wrapper for FutureBuilder
  • StreamWorker: Simplified wrapper for StreamBuilder
  • State Widgets: OptionLoading, OptionEmpty, OptionError
  • Error Handling: Automatic error management with customizable error builders and retry option
  • Production Mode: Hides empty states in production

Installation #

dependencies:
  builder_plus: <latest_version>

Example #

Here is a complete example using all main features of the package:

import 'package:flutter/material.dart';
import 'package:builder_plus/builder_plus.dart';
import 'package:builder_plus/model/option_loading.dart';
import 'package:builder_plus/model/option_empty.dart';
import 'package:builder_plus/model/option_error.dart';

// A custom class to illustrate typing
typedef Id = int;

class Example {
  final Id id;
  final String name;

  Example(this.id, this.name);

  @override
  String toString() => 'Example(id: $id, name: $name)';
}

// Simulate an asynchronous operation on an Example
Future<Example?> processExample(Example example, {bool throwError = false, bool empty = false}) async {
  await Future.delayed(const Duration(seconds: 2));
  if (throwError) throw Exception('Network error');
  if (empty) return null;
  return Example(example.id, example.name.toUpperCase());
}

// Simulate a stream of operations on an Example
Stream<Example?> streamExample(Example example, {bool throwError = false, bool empty = false}) async* {
  yield null;
  await Future.delayed(const Duration(seconds: 1));
  if (throwError) throw Exception('Stream error');
  if (empty) yield null;
  else yield Example(example.id, example.name.toUpperCase());
  await Future.delayed(const Duration(seconds: 1));
  yield Example(example.id, ' [1m${example.name.toUpperCase()} (final)');
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'builder_plus Example',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const ExampleScreen(),
    );
  }
}

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

  @override
  State<ExampleScreen> createState() => _ExampleScreenState();
}

class _ExampleScreenState extends State<ExampleScreen> {
  bool throwFutureError = false;
  bool throwStreamError = false;
  bool showEmpty = false;

  @override
  Widget build(BuildContext context) {
    final example = Example(1, 'demo');
    return Scaffold(
      appBar: AppBar(title: const Text('FutureWorker & StreamWorker Example')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Checkbox(
                  value: throwFutureError,
                  onChanged: (v) => setState(() => throwFutureError = v ?? false),
                ),
                const Text('Future Error'),
                const SizedBox(width: 16),
                Checkbox(
                  value: throwStreamError,
                  onChanged: (v) => setState(() => throwStreamError = v ?? false),
                ),
                const Text('Stream Error'),
                const SizedBox(width: 16),
                Checkbox(
                  value: showEmpty,
                  onChanged: (v) => setState(() => showEmpty = v ?? false),
                ),
                const Text('Empty State'),
              ],
            ),
            const SizedBox(height: 24),
            const Text('FutureWorker', style: TextStyle(fontWeight: FontWeight.bold)),
            FutureWorker<Example?>(
              future: processExample(example, throwError: throwFutureError, empty: showEmpty),
              builder: (context, result) => Text(
                'After processing: \n [1m [0m${result.toString()}',
                style: const TextStyle(fontSize: 18),
              ),
              loadingBuilder: (context) => const OptionLoading(size: 32),
              emptyBuilder: (context) => const OptionEmpty(
                text: 'No data received',
                icon: Icons.hourglass_empty,
              ),
              errorBuilder: (context, error) => OptionError(
                error: error,
                onRetry: () => setState(() {}),
                retryButtonText: 'Retry',
              ),
            ),
            const SizedBox(height: 32),
            const Text('StreamWorker', style: TextStyle(fontWeight: FontWeight.bold)),
            StreamWorker<Example?>(
              stream: streamExample(example, throwError: throwStreamError, empty: showEmpty),
              builder: (context, result) => Text(
                'Stream: \n${result.toString()}',
                style: const TextStyle(fontSize: 18),
              ),
              loadingBuilder: (context) => const OptionLoading(size: 32, color: Colors.orange),
              emptyBuilder: (context) => const OptionEmpty(
                text: 'Empty stream',
                icon: Icons.stream,
                iconColor: Colors.orange,
              ),
              errorBuilder: (context, error) => OptionError(
                error: error,
                onRetry: () => setState(() {}),
                retryButtonText: 'Restart stream',
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Error Handling #

Both FutureWorker and StreamWorker provide flexible error handling:

  • Custom Error Builder: Use errorBuilder to provide a custom widget when an error occurs. The builder receives the error object (Object?) as the second parameter.
  • Default Error Widget: If no errorBuilder is provided, the widgets automatically display OptionError with the error message.
  • OptionError Widget: The OptionError widget accepts any error object type and automatically converts it to a displayable string.

Example with custom error handling:

FutureWorker<User>(
  future: userService.getUser(id),
  builder: (context, user) => UserCard(user: user),
  errorBuilder: (context, error) => Column(
    children: [
      Icon(Icons.error, color: Colors.red),
      Text('Failed to load user: ${error?.toString() ?? "Unknown error"}'),
      ElevatedButton(
        onPressed: () => setState(() {}),
        child: Text('Retry'),
      ),
    ],
  ),
)

Parameters #

FutureWorker / StreamWorker #

Parameter Type Description
`future stream` `Future
builder Widget Function(BuildContext, T) Builder for the data
isProd bool Production mode (hides empty states)
initialData T? Initial data
emptyBuilder Widget Function(BuildContext)? Builder for empty state
errorBuilder Widget Function(BuildContext, Object?)? Builder for error state (receives the error object)
loadingBuilder Widget Function(BuildContext)? Builder for loading

OptionLoading #

Parameter Type Default Description
size double 24.0 Indicator size
color Color? null Color (uses theme if null)

OptionEmpty #

Parameter Type Default Description
text String - Text to display
icon IconData? null Optional icon
textStyle TextStyle? null Text style
iconSize double 48.0 Icon size
iconColor Color? null Icon color
spacing double 16.0 Spacing

OptionError #

Parameter Type Default Description
error Object? - Error object or message (automatically converted to string)
onRetry VoidCallback? null Retry callback
textStyle TextStyle? null Text style
iconSize double 48.0 Icon size
iconColor Color? null Icon color
spacing double 16.0 Spacing
showRetryButton bool true Show retry button
retryButtonText String 'Retry' Retry button text

Note: The error parameter accepts any object type. If it's a String, it will be displayed directly. If it's an Exception or other object, it will be converted to a string automatically. If null, it will display "An unknown error occurred".

1
likes
155
points
120
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for simplified async operations with built-in loading, error, and empty states management.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on builder_plus