declarative_async_widget 0.4.0 copy "declarative_async_widget: ^0.4.0" to clipboard
declarative_async_widget: ^0.4.0 copied to clipboard

FutureWidget and StreamWidget - Declarative FutureBuilder and StreamBuilder

FutureWidget and StreamWidget - Improved FutureBuilder and StreamBuilder. #

This package provides FutureWidget and StreamWidget, two widgets similar to FutureBuilder and StreamBuilder, which are designed to reduce boilerplate and improve error handling. It started as a rewrite of the package async_builder.

How it differs from async_builder

Although this package started as a rewrite, async_builder and declarative_async_widget are very different packages now.

In async_builder, multiple mutually exclusive events can coexist (e.g. giving both a stream and a future (which throws a runtime error), assigning both a waiting and an initialData, defining stream-only relevant params with a Future, ...). Some of these mutually exclusive events can coexists also when using FutureBuilder and StreamBuilder.

declarative_async_widget provides clear and easy-to-use declarative alternatives to AsyncWidget and to Dart's built-in FutureBuilder and StreamBuilder, effectively eliminating the occurrence of multiple mutually-exclusive events.

How to use #

1. Add to dependencies

dependencies:
  declarative_async_widget: ^latest # replace latest with version number

2. Import

import 'package:declarative_async_widget/declarative_async_widget.dart';

FutureWidget #

The future can't be null.

Constructors #

All constructors and an example of their usage are shown below. Every snippet includes only the required parameters.

Default constructor

whenData, whenError and whenLoading are required. initialData can't be assigned since whenLoading is going to be used instead.

class ExampleWidget extends StatefulWidget {
  const ExampleWidget({Key? key}) : super(key: key);

  @override
  ExampleWidgetState createState() => ExampleWidgetState();
}

class ExampleWidgetState extends State<ExampleWidget> {
  final Future<int> _getInt = myFuture(false);

  static Future<int> myFuture(bool shouldThrow) async {
    await Future.delayed(const Duration(seconds: 4));
    if (shouldThrow) {
      throw Exception("You can't pass true as a param");
    }
    return 4;
  }

  @override
  Widget build(BuildContext context) {
    return FutureWidget<int>(
      future: _getInt,
      whenData: (context, data) => Text('My data is: $data'),
      whenError: (context, error, stackTrace) => Text('Error was thrown: $error'),
      whenLoading: (context) => const Text('Loading...'),
    );
  }
}

noError constructor

There is no whenError param. Make 100% sure the future can't throw an error, else an AsyncWidgetUnexpectedError will be thrown at runtime.

class ExampleWidgetState extends State<ExampleWidget> {
  final Future<int> _getInt = myFuture();

  static Future<int> myFuture() async {
    await Future.delayed(const Duration(seconds: 4));
    return 4;
  }

  @override
  Widget build(BuildContext context) {
    return FutureWidget<int>.noError(
      future: _getInt,
      whenData: (context, data) => Text('My data is: $data'),
      whenLoading: (context) => const Text('Loading...'),
    );
  }
}

noLoading constructor

There is no whenLoading param, since in this case some initial data is present/given before the future has even fired; an initialData argument is therefore required.

class ExampleWidgetState extends State<ExampleWidget> {
  final Future<int> _getInt = myFuture(true);

  static Future<int> myFuture(bool shouldThrow) async {
    await Future.delayed(const Duration(seconds: 4));
    if (shouldThrow) {
      throw Exception("You can't pass true as a param");
    }
    return 4;
  }

  @override
  Widget build(BuildContext context) {
    return FutureWidget<int>.noLoading(
      future: _getInt,
      whenData: (context, data) => Text('My data is: $data'),
      initialData: 99999,
      whenError: (BuildContext context, Object error, StackTrace? stackTrace) =>
          Text('Error was thrown: $error'),
    );
  }
}

onlyData constructor

This constructor integrates noError and noLoading. Therefore:

  • no whenError param
  • no whenLoading param
  • initialData is required
class ExampleWidgetState extends State<ExampleWidget> {
  final Future<int> _getInt = myFuture();

  static Future<int> myFuture() async {
    await Future.delayed(const Duration(seconds: 4));
    return 4;
  }

  @override
  Widget build(BuildContext context) {
    return FutureWidget<int>.onlyData(
      future: _getInt,
      whenData: (context, data) => Text('My data is: $data'),
      initialData: 99999,
    );
  }
}

Additional parameters #

  • disposeOnFutureChange

    Type: bool

    Whether or not the state of this widget should be disposed (and therefore the widget rebuilt) when the future instance changes. If false, the current data should be retained.

    Default value: true.

  • printErrorsToConsole

    Type: bool

    Whether or not to print errors to the console.

    Default value:

    • false with the default and noLoading constructors
    • true with the noError and onlyValue constructors (can't be set to false in these cases: in case of unexpected errors they will be printed)
  • reportError

    Type: void Function(FlutterErrorDetails details)

    If provided, overrides the function that prints errors to the console.

    Default value: null

  • keepAlive

    Type: bool

    Whether or not we should send a keep alive notification with AutomaticKeepAliveClientMixin.

    Default value: false

StreamWidget #

It is similar to FutureWidget, but uses a Stream instead of a Future.

The parameter stream can't be null.

Constructors #

Like with FutureWidget, all constructors and an example of their usage are listed below. Every snippet includes only the required parameters.

Default constructor

whenData, whenError and whenLoading are required. initialData can't be assigned since whenLoading is going to be used instead.

class ExampleWidget extends StatefulWidget {
  const ExampleWidget({Key? key}) : super(key: key);

  @override
  ExampleWidgetState createState() => ExampleWidgetState();
}

int a = 4; // global variable

class ExampleWidgetState extends State<ExampleWidget> {
  final Stream<int> _getInt =
      Stream<int>.periodic(const Duration(seconds: 2), (whatIsThis) {
    print(whatIsThis);
    ++a;
    print(a);
    if (a == 8 || a == 12) {
      throw Exception("8 and 12 throw an exception");
    }
    return a;
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        StreamWidget<int>(
          stream: _getInt,
          whenData: (context, data) => Text('My data is: $data'),
          whenError: (context, error, stackTrace) =>
              Text('Error was thrown: $error'),
          whenLoading: (context) => const Text('Loading...'),
        ),
      ],
    );
  }
}

noError constructor

There is no whenError param. Make 100% sure the future can't throw an error, else an AsyncWidgetUnexpectedError will be thrown at runtime.

int a = 4; // global variable

class ExampleWidgetState extends State<ExampleWidget> {
  final Stream<int> _getInt =
      Stream<int>.periodic(const Duration(seconds: 2), (whatIsThis) {
    print(whatIsThis);
    ++a;
    print(a);
    if (a == 8 || a == 12) {
      throw Exception("8 and 12 throw an exception");
    }
    return a;
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        StreamWidget<int>.noError(
          stream: _getInt,
          whenData: (context, data) => Text('My data is: $data'),
          whenLoading: (context) => const Text('Loading...'),
        ),
      ],
    );
  }
}

This example will throw an AsyncWidgetUnexpectedError once a reaches 8 (with an error screen in the debug build). Additionally, the stream will be immediately closed because the param cancelOnError is always true when using the noError (or the onlyData) constructor.

noLoading constructor

There is no whenLoading param, since in this case some initial data is present/given before the stream has even fired; an initialData argument is therefore required.

int a = 4; // global variable

class ExampleWidgetState extends State<ExampleWidget> {
  final Stream<int> _getInt =
      Stream<int>.periodic(const Duration(seconds: 2), (whatIsThis) {
    print(whatIsThis);
    ++a;
    print(a);
    if (a == 8 || a == 12) {
      throw Exception("8 and 12 throw an exception");
    }
    return a;
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        StreamWidget<int>.noLoading(
          stream: _getInt,
          whenData: (context, data) => Text('My data is: $data'),
          whenError: (context, error, stackTrace) =>
              Text('Error was thrown: $error'),
          initialData: 9,
        ),
      ],
    );
  }
}

onlyData constructor

This constructor integrates noError and noLoading. Therefore:

  • no whenError param
  • no whenLoading param
  • initialData is required
int a = 4; // global variable

class ExampleWidgetState extends State<ExampleWidget> {
  final Stream<int> _getInt =
      Stream<int>.periodic(const Duration(seconds: 2), (whatIsThis) {
    return ++a;
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        StreamWidget<int>.onlyData(
          stream: _getInt,
          initialData: 0,
          whenData: (context, data) => Text('My data is: $data'),
        ),
      ],
    );
  }
}

Additional parameters #

Similarly to FutureWidget:

  • disposeOnStreamChange

    Type: bool

    Whether or not the state of this widget should be disposed (and therefore the widget rebuilt) when the stream instance changes. If false, the current data should be retained.

    Default value: true

  • printErrorsToConsole

    Type: bool

    Whether or not to print errors to the console.

    Default value:

    • false with the default and noLoading constructors
    • true with the noError and onlyValue constructors (can't be set to false in these cases: in case of unexpected errors they will be printed)
  • reportError

    Type: void Function(FlutterErrorDetails details)

    If provided, overrides the function that prints errors to the console.

    Default value: null

  • keepAlive

    Type: bool

    Whether or not we should send a keep alive notification with AutomaticKeepAliveClientMixin.

    Default value: false

StreamWidget-specific parameters:

  • paused

    Type: bool

    Whether or not the stream subscription should be paused.

    Default: false

  • whenClosedOnData

    Type: Widget Function(BuildContext context, T data)

    The builder that should be called when the stream is closed and the last emitted value was some data of type [T].

    If null, it defaults to whenData.

    Default value: null

  • whenClosedOnError

    Type: Widget Function(BuildContext context, Object error, StackTrace? stackTrace)

    The builder that should be called when the stream is closed and the last emitted value was an error.

    If null, it defaults to whenError.

    Only accessible in:

    • default constructor
    • noLoading constructor

    Default value: null

  • whenClosedOnLoading

    Type: Widget Function(BuildContext context)

    The builder that should be called when the stream is closed without having emitted any value (neither data nor error), which is is an edge case.

    If null and a stream closes without emitting anything, a StreamClosedOnLoadingError will be thrown.

    Only accessible in:

    • default constructor
    • noError constructor

    Default value: null

  • cancelOnError

    Type: bool

    If set to true, the subscription is automatically canceled when the first error event is delivered. The default is false.

    If set to true, you probably also want to set printErrorsToConsole to true.

    If true, then you only have to define either whenClosedOnError or whenError (since their functionality overlaps in this case).

    Default value:

    • false with the default and noLoading constructors
    • true with the noError and onlyValue constructors (can't be set to false in these cases since it is assigned in the initialization list)

Issues with type inference #

Remember to specify the type, e.g., FutureWidget<int>.onlyData and not FutureWidget.onlyData, otherwise a dynamic or a nullable type might be inferred (e.g. the type returned by future is int, however FutureWidget's inferred type is int?, i.e., FutureWidget<int?>).

Note #

Tests will be added in the future.

1
likes
150
pub points
0%
popularity

Publisher

verified publishermanuelplavsic.ch

FutureWidget and StreamWidget - Declarative FutureBuilder and StreamBuilder

Repository (GitLab)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, rxdart

More

Packages that depend on declarative_async_widget