Makes it possible to safely execute and retry a Future inside a StatelessWidget

Related tags

Templates futuristic
Overview

futuristic

Makes it possible to safely execute and retry a Future inside a StatelessWidget.

See the Mainstream package for a similar API for working with Streams.

Problem

If you've ever tried to use the FutureBuilder widget in Flutter, you've probably been surprised by its behavior. When used inside a StatelessWidget, it will re-execute its Future every time it is rebuilt. Since a widget can be rebuilt many times in Flutter (including due to hot reload), this can be undesirable if our Future calls a non-idempotent REST API endpoint, for example.

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: myExpensiveFuture(),  //Will be executed every time Home is rebuilt
      builder: (_context, snapshot) {
        ...
      },
    );
  }
}

To execute our Future only once, we could use a StatefulWidget, but now we have the extra boilerplate of using a StatefulWidget and holding onto our Future in a state variable.

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  Future _future;

  @override
  void initState() {
    super.initState();
    _future = myExpensiveFuture();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _future,  // Will be executed only once
      builder: (_context, snapshot) {
        ...
      },
    );
  }
}

Solution

The problem with FutureBuilder is, ironically, that it takes a Future instance as its input. Instead, the Futuristic widget takes a Function that returns a Future and holds onto it in its own State. This means:

  • It can be used in a StatelessWidget.
  • It can let child widgets start or retry a Future.

Additionally, Futuristic provides:

  • Multiple builder callbacks to provide mutually exclusive initial/busy/data/error widget states.
  • Optional onData/onError callbacks to perform additional actions when a Future succeeds or fails.
  • Generic type safety for the data provided to callbacks. The type parameter <T> can be omitted if it can be inferred from the futureBuilder function.

Usage

We can use the Futuristic widget to wrap a single component like a button, or even an entire screen.

Button example

To start executing a Future in response to a button press, connect its onPressed handler to the start function provided in the initialBuilder callback.

Future<int> myFuture(int first, int second) async {
  await Future.delayed(const Duration(seconds: 1));
  return first + second;
}

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Futuristic<int>(
      futureBuilder: () => myFuture(1, 2),
      initialBuilder: (context, start) => RaisedButton(child: Text('Go'), onPressed: start),
      busyBuilder: (context) => CircularProgressIndicator(),
      errorBuilder: (context, error, retry) => RaisedButton(child: Text('Oops'), onPressed: retry),
      dataBuilder: (context, data) => Text(data.toString()),
    );
  }
}

The futureBuilder parameter is required.

The initialBuilder parameter is required if autoStart is false (see example below).

The optional busyBuilder displays a widget when the Future is busy executing. By default, it shows a centered CircularProgressIndicator. By displaying this, we inform the user that the operation is in progress and also prevent the Future from being triggered twice accidentally.

The optional errorBuilder displays a widget when the Future has failed, typically with an Error or Exception. This is provided as a parameter, together with a retry function that can be called to "retry" the Future.

The optional dataBuilder displays a widget when the Future has succeded. The resulting T value of the Future<T> is provided as a parameter to the callback. Note that this will be null in the case of a Future<void>.

Screen example

To automatically start executing a Future upon navigating to a screen, set the autoStart parameter to true instead of providing an initialBuilder. The busyBuilder will be displayed immediately.

Future<int> myFuture(int first, int second) async {
  await Future.delayed(const Duration(seconds: 1));
  throw Exception('something happened');
  return first + second;
}

class MyScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Futuristic<int>(
      autoStart: true,
      futureBuilder: () => myFuture(1, 2),
      busyBuilder: (context) => CircularProgressIndicator(),
      onError: (error, retry) => showDialog(...),
      onData: (data) => showDialog(...),
    );
  }
}

The futureBuilder parameter is required.

The optional onError callback can be used to handle the error event by displaying an alert dialog or sending to a logging provider, for example. It can be used in place of or together with the errorBuilder. A retry function is provided as a parameter that can be called to "retry" the Future. Be careful not to call retry without user interaction to avoid creating an infinite loop. This callback will not be retriggered as a result of a widget rebuild.

The optional onData callback can be used to handle a successful result by displaying an alert dialog or performing navigation. for example. This can be used in place of or together with the dataBuilder. This callback will not be retriggered as a result of a widget rebuild.

Comments
  • Feature request for StreamBuilder

    Feature request for StreamBuilder

    This is the kind of addon I was looking for to solve an issue with my usage of Cloud Firestore, where my database streams keep getting rebuilt every time there's a state update, which has been causing glitches and wasted database calls. Wanted an adaptation of this to work with StreamBuilder to do the same as it would for Futuristic.. The implementation of a FutureBuilder and StreamBuilder are almost the same, so I figured it'd be an easy change..

    I was going to ask you to do it, but I peaked into your code and forked it over to add the feature myself easily. Called it Streamistic, but could also be called Streamtastic or Streamist.. Your package name Futuristic sounds better, so feel free to include if you like. I basically replaced the word future with stream in your code and it pretty much worked out. I haven't tested it yet, but looks like it should. I could do a PR after I make sure, but my added file is here: https://github.com/Skquark/futuristic/blob/master/lib/streamistic.dart Thanks for coming up with this method, clever implementation. I have a lot of replacing of StreamBuilder to do in my projects (assuming it works).

    opened by Skquark 3
  • There is no test for autostart = true

    There is no test for autostart = true

    trivial but important to test the good cases

       testWidgets(
            'shows dataBuilder after future completes successfully with autostart',
            (tester) async {
          final text = '3';
          final widget = MaterialApp(
            home: Futuristic(
              futureBuilder: () => goodFuture(),
              autoStart: true,
              busyBuilder: (_) => CircularProgressIndicator(),
              dataBuilder: (_, data) => Text(text),
            ),
          );
          await tester.pumpWidget(widget);
          await tester.pump();
          await tester.pump();
          expect(find.text(text), findsOneWidget);
        });
    
    opened by ride4sun 0
  • Can I cascade futuristic widgets?

    Can I cascade futuristic widgets?

    Should that work:

      Widget build(BuildContext context) => new BlocProvider(
            bloc: appBloc,
            child: new LoggingBlocProvider(
              bloc: loggingBloc,
              child: Futuristic(
                autoStart: true,
                futureBuilder: () => appBloc.initBeforeUIStart(),
                busyBuilder: (context) => Text('busy1'),
                onError: (error, retry) => Text('error1'),
                onData: (data) => Futuristic(
                    autoStart: true,
                    futureBuilder: () => initBloc(),
                    onData: (data) => _buildMain(context),
                    onError: (error, retry) => Text('error2'),
                    busyBuilder: (context) => _buildMain(context)),
              ),
            ),
          );
    
    opened by ride4sun 0
  • Need option to Refresh within the dataBuilder as well.

    Need option to Refresh within the dataBuilder as well.

    There is no option for performing refresh operations within the data builder like in errorBuilder. It would be nice to have option to recall the same future or provide future to be executed let's say for performing pagination.

    The Scenario Came when I was trying to implement Pull to Refresh, to refresh data from API as the changes were more frequent.

    opened by spike04 2
  • Futuristic Part of a Widget won't disappear

    Futuristic Part of a Widget won't disappear

    I hope I can describe my problem correctly, because it is somehow weird

    I have a GridView with a few Items shown on screen. A single item is a Column with a Image and a Button. The Image is the Futuristic Part. When clicking on the Button the complete Item is to disappear. Everything works fine when I click on the last Item-Button. But when clicking on an other Button somewhere in the middle then the Image won't disappear and the the Buttons/Images get shifted (they do not match anymore)

    Here is a Screenshot of what I mean. On the right side the Images do not match the Text/Button (Star) because the Image did not disappear as it should have. I clicked on the first Button from the left Picture grafik

    When switching from Futuristic to FutureBuilder everything is working like intended. I am not sure if this is a Bug, since Futuristic is meant to not execute the future on rebuilding but it seems pretty weird to me and I would really like to make it work with Futuristic

    Is my "problem" clear?

    opened by filly82 2
Owner
Martin Rybak
Martin Rybak
Dart package for Async Data Loading and Caching. Combine local (DB, cache) and network data simply and safely.

Stock is a dart package for loading data from both remote and local sources. It is inspired by the Store Kotlin library.

xmartlabs 59 Dec 24, 2022
Know where to go safely. Describe your experiences and rate places.

Is It Safe? ?? Índice Sobre Showcase Features Como eu posso rodar o projeto? Ferramentas Instalação Run Suporte Como posso contribuir? Autores Info ??

Is It Safe? 0 Sep 19, 2022
An HTTP file downloader packed with many features -> resumable downloads, multiple connections, buffering, auto-retry, etc.

An HTTP file downloader packed with many features -> resumable downloads, multiple connections, buffering, auto-retry, etc. Features Resumable downloa

Liu YuanYuan 4 Feb 2, 2022
A Flutter plugin which allows you to execute code in the background on Android and iOS.

Flutter Workmanager Flutter WorkManager is a wrapper around Android's WorkManager, iOS' performFetchWithCompletionHandler and iOS BGAppRefreshTask, ef

Flutter Community 699 Jan 5, 2023
Flutter After Layout - Brings functionality to execute code after the first layout of a widget has been performed

Flutter After Layout - Brings functionality to execute code after the first layout of a widget has been performed, i.e. after the first frame has been displayed. Maintainer: @slightfoot

Flutter Community 432 Jan 3, 2023
In this app, it's possible to find some different kind of flutter animations

flutter_animations Apprendre à utiliser les animations avec Flutter Getting Started This project is a starting point for a Flutter application. A few

MOISE Rajesearison 0 Dec 24, 2021
Another breakpoint framework. Aims to simplify as much as possible building adaptive layouts.

Another breakpoint framework. Aims to simplify as much as possible building adaptive layouts. Features Really simple implementation Works with and wit

null 3 Sep 26, 2022
AdventOfCode 2022 in Dart focusing on code golf, making the solutions as small as possible

Advent of Code 2022 in Dart (Code Golf) This is my attempt at solving the Advent of Code 2022 puzzles in the shortest possible code using Dart 2.18. Y

Pascal Welsch 5 Dec 15, 2022
Fly towards a more connected future with Wings HQ :D

Flying towards a more connected future For Project Presentation(website) Click here For Project Demo Video(youtube) : Click Here ?? CONTENT: - - - - -

Samrat Mukherjee 4 Oct 3, 2021
A basic login/register screen that can be used as a template for future Flutter projects.

A Flutter / Firebase login screen A simple flutter/dart based login-screen that connects with Firebase Auth to enable users to sign-in/up with Email o

Kim Andre Langholz 142 Dec 20, 2022
A future based dart package for the NLT (New Living Translation) API.

NLT API Bible for Dart A future based dart package for the NLT API from Tyndale which can be used to fetch NLT and KJV bible passages. This package re

Ariel Magbanua 3 Nov 17, 2022
A flutter plugin to draw the coordinates on the widget and as well as to find the given point is inside a list of coordinates or not.

Draw On A flutter plugin to draw the coordinates on widget and as well as to find the given point is inside a list of coordinates or not. For Draw on

Sivaramsiva10 4 Apr 5, 2022
Iridium-reader-widget - Plug and play reader widget allowing to easily integrate an Iridium viewer inside any app

Plug and play reader widget allowing to easily integrate an Iridium viewer insid

Mantano 15 Dec 31, 2022
Boris Gautier 1 Jan 31, 2022
Allows communication between your bot and the Web App built in Flutter displayed inside Telegram.

tele_web_app It enables communication between your bot and the Flutter-embedded Web App displayed inside Telegram by making use of interoperability be

Yeikel Uriarte Arteaga 16 Dec 8, 2022
Working proof of the Go (golang) server running inside Flutter

flap Working proof of the Go server running inside Flutter Video in action Prerequisites Flutter >2.0 Go >1.16 Build Go server cd go macOS: make maco

Err 28 Dec 17, 2022
A Flutter package that provides a dropdown form field using a dropdown button inside a form field.

Dropdown form field A dropdown form field using a dropdown button inside a form field. Demo Features Can be used as regular form field. Simple to impl

Carlos Eugenio Torres 72 Jan 1, 2023
Allows tags to be entered inside textfield

textfield_tags This is a widget that allows your users to create tags by entering the tag's name inside of textfield and make the tags appear in the t

Eyoel Defare 75 Nov 2, 2022