declarative_async_widget 0.4.0 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. Iffalse
, 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 andnoLoading
constructorstrue
with thenoError
andonlyValue
constructors (can't be set tofalse
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. Iffalse
, 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 andnoLoading
constructorstrue
with thenoError
andonlyValue
constructors (can't be set tofalse
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 towhenData
.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 towhenError
.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 isfalse
.If set to
true
, you probably also want to setprintErrorsToConsole
totrue
.If
true
, then you only have to define eitherwhenClosedOnError
orwhenError
(since their functionality overlaps in this case).Default value:
false
with the default andnoLoading
constructorstrue
with thenoError
andonlyValue
constructors (can't be set tofalse
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.