blob: 8fd063d89a6cc6d9cf07ac344daea7110b53f4c9 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
/// A Builder which is invoked when the [FutureWidget] needs to display a
/// child which represents an error state.
typedef ErrorBuilder = Widget Function(BuildContext context, Error error);
/// A widget that provides a [child] widget when it's [Future] completes.
class FutureWidget extends StatefulWidget {
/// Holds the [Future] or [Widget] instance.
final FutureOr<Widget> child;
/// Holds the [Widget] that is a place holder until [child] future completes.
final Widget loadingWidget;
/// A builder which is invoked when the [Future] results in an error and
/// the Widget needs to display an error widget.
final ErrorBuilder errorBuilder;
/// Constructor.
const FutureWidget({
@required this.child,
this.loadingWidget = const Offstage(),
this.errorBuilder = _emptyErrorBuilder,
}) : assert(child != null);
_FutureWidgetState createState() => new _FutureWidgetState();
class _FutureWidgetState extends State<FutureWidget> {
Widget _child;
Error _error;
void initState() {
void didUpdateWidget(FutureWidget oldWidget) {
Widget build(BuildContext context) {
return _error != null ? widget.errorBuilder(context, _error) : _child;
void _initWidget() {
if (widget.child is Widget) {
_child = widget.child;
} else {
// In case this state instance is reparented to another widget when the
// future completes, cache the current parent widget.
FutureWidget parentWidget = widget;
_loadWidget(parentWidget.child, (Widget child) {
if (parentWidget == widget && mounted) {
setState(() => _child = child);
_child = widget.loadingWidget;
void _loadWidget(Future<Widget> child, void callback(Widget widget)) {
child.then(callback, onError: (Error error) {
// Since this future can fire at any time we need to ensure that we are
// mounted before calling setState since it is considered an error to call
// setState when mounted is false
if (mounted) {
setState(() => _error = error);
} else {
_error = error;
Widget _emptyErrorBuilder(BuildContext context, Error error) {
return const Offstage();