3

I am using a flutter widget's initState() method to create two future ints; delayed which is a simple Future.delay, and delayedAwaited, which awaits on the delayed variable and doubles its value. The build() method includes two FutureBuilders, one for each future. See example code below:

class FutureTest extends StatefulWidget {
  @override
  _FutureTestState createState() => _FutureTestState();
}

class _FutureTestState extends State<FutureTest> {
  Future<int>? delayed;
  Future<int>? delayedAwaited;

  @override
  void initState() {
    super.initState();
    initFutures();
  }

  void initFutures() async {
    delayed = Future.delayed(Duration(seconds: 2), () => 5);
    delayedAwaited = Future.value((await delayed)! * 2);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        FutureBuilder(
          future: delayed,
          builder: (context, snapshot) => Text('delayed: ${snapshot.data}'),
        ),
        FutureBuilder(
          future: delayedAwaited,
          builder: (context, snapshot) => Text('awaited: ${snapshot.data}'),
        ),
      ],
    );
  }
}

Here is the problem: The UI only updates for delayed and not delayedAwaited (it remains as null). The UI is updated to reflect the true value when manually hot-reloading, so the future is resolving correctly, but Flutter is just not triggering the FutureBuilder to rebuild. My expectation would be that the UI updates for delayAwaited as well.

See the behavior here: https://dartpad.dev/62b272db200ca39ed854be5a7183967d?null_safety=true

For some reason, changing initFutures() so it uses Future.delayed for both futures fixes the issue, as shown here: https://dartpad.dev/9b386a45428046b193800351a4ade5b1?null_safety=true

Is anyone able to explain why the given code works like this, and what is best practice for achieving what I am trying to do.

  • 1
    I'm guessing that delayedAwaited is never a future (since you await in front of it) so it goes right from null to the completed value. Or something like that. Get rid of await, and switch to a .then to set delayedAwaited. Or I could be confused by your example. :) – Randal Schwartz Jan 16 '21 at 04:03
  • As I understand it, async functions remove the need to use the .then pattern. The delayAwaited should be set to a future immediately, as I believe that is the behaviour of async functions. Is there a way to achieve this using only the async/await syntax? Source: https://youtu.be/SmTCmDMi4BY?t=90 – Elliot Sayes Jan 16 '21 at 05:04
  • Definitely still need for .then syntax sometimes. – Randal Schwartz Jan 16 '21 at 05:10
  • adding `print(delayed); print(delayedAwaited);`at the first line of `build()` method will explain why that happens – pskink Jan 16 '21 at 05:53
  • @pskink you're exactly right, delayedAwaited shows as null. Is there a way to immediately assign it to a future that depends on another future's value, using the async/await semantics? – Elliot Sayes Jan 16 '21 at 08:28

1 Answers1

2

You can do it this way since you mentioned in the comments that you want to use async/await only:

 void initFutures() {
    delayed = fordelayed();
    delayedAwaited = fordelayedAwaited();
    
  }
  
  Future<int> fordelayed() async {
      return await Future.delayed(Duration(seconds: 2), () => 5);
  }
  
  Future<int> fordelayedAwaited() async {
      int fromdelayed = await fordelayed();
      return fromdelayed * 2;
  }
Calvin Gonsalves
  • 1,738
  • 11
  • 15
  • 2
    I think this is the best solution. You can simplify further by removing `async` from `initFutures()`. I ended up going with the following syntax, for brevity: `delayedAwaited = Future(() async => (await delayed)! * 2);`. The pattern is to assign futures without using async/await directly, but use them within the callback functions (named as yours, or anonymous as mine). – Elliot Sayes Jan 17 '21 at 02:16
  • I agree, the `async` from `initFutures()` is not needed. I will update it. – Calvin Gonsalves Jan 17 '21 at 03:28