Dart, how to create a future to return in your own functions?
Asked Answered
G

6

97

is it possible to create your own futures in Dart to return from your methods, or must you always return a built in future return from one of the dart async libraries methods?

I want to define a function which always returns a Future<List<Base>> whether its actually doing an async call (file read/ajax/etc) or just getting a local variable, as below:

List<Base> aListOfItems = ...;

Future<List<Base>> GetItemList(){

    return new Future(aListOfItems);

}
Gemma answered 24/8, 2013 at 22:17 Comment(0)
P
157

If you need to create a future, you can use a Completer. See Completer class in the docs. Here is an example:

Future<List<Base>> GetItemList(){
  var completer = new Completer<List<Base>>();
    
  // At some time you need to complete the future:
  completer.complete(new List<Base>());
    
  return completer.future;
}

But most of the time you don't need to create a future with a completer. Like in this case:

Future<List<Base>> GetItemList(){
  var completer = new Completer();
    
  aFuture.then((a) {
    // At some time you need to complete the future:
    completer.complete(a);
  });
    
  return completer.future;
}

The code can become very complicated using completers. You can simply use the following instead, because then() returns a Future, too:

Future<List<Base>> GetItemList(){
  return aFuture.then((a) {
    // Do something..
  });
}

Or an example for file io:

Future<List<String>> readCommaSeperatedList(file){
  return file.readAsString().then((text) => text.split(','));
}

See this blog post for more tips.

Psychoactive answered 24/8, 2013 at 23:6 Comment(0)
M
85

You can simply use the Future<T>value factory constructor:

return Future<String>.value('Back to the future!');
Moir answered 19/12, 2018 at 14:23 Comment(3)
Maybe I'm doing something wrong, but I get the .value('Back to the future!') flagged as dead code.Qintar
but in the position of receiving call back, how could we handle the <T> object, in your case a <String> (Back to the future!)?Kashakashden
This solution was the simplest for me. I did <br/> return Future<List<MyClass>>.value(new List<MyClass>());Monnet
P
50

Returning a future from your own function

This answer is a summary of the many ways you can do it.

Starting point

Your method could be anything but for the sake of these examples, let's say your method is the following:

int cubed(int a) {
  return a * a * a;
}

Currently you can use your method like so:

int myCubedInt = cubed(3);  // 27

However, you want your method to return a Future like this:

Future<int> myFutureCubedInt = cubed(3);

Or to be able to use it more practically like this:

int myCubedInt = await cubed(3);

The following solutions all show ways to do that.

Solution 1: Future() constructor

The most basic solution is to use the generative constructor of Future.

Future<int> cubed(int a) {
  return Future(() => a * a * a);
}

I changed the return type of the method to Future<int> and then passed in the work of the old function as an anonymous function to the Future constructor.

Solution 2: Future named constructor

Futures can complete with either a value or an error. Thus if you want to specify either of these options explicitly you can use the Future.value or Future.error named constructors.

Future<int> cubed(int a) {
  if (a < 0) {
    return Future.error(ArgumentError("'a' must be positive."));
  }
  return Future.value(a * a * a);
}

Not allowing a negative value for a is a contrived example to show the use of the Future.error constructor. If there is nothing that would produce an error then you can simply use the Future.value constructor like so:

Future<int> cubed(int a) {
  return Future.value(a * a * a);
}

Solution 3: async method

An async method automatically returns a Future so you can just mark the method async and change the return type like so:

Future<int> cubed(int a) async {
  return a * a * a;
}

Normally you use async in combination with await, but there is nothing that says you must do that. Dart automatically converts the return value to a Future.

In the case that you are using another API that returns a Future within the body of your function, you can use await like so:

Future<int> cubed(int a) async {
  return await cubedOnRemoteServer(a);
}

Or this is the same thing using the Future.then syntax:

Future<int> cubed(int a) async {
  return cubedOnRemoteServer(a).then((result) => result);
}

Solution 4: Completer

Using a Completer is the most low level solution. You only need to do this if you have some complex logic that the solutions above won't cover.

import 'dart:async';

Future<int> cubed(int a) async {
  final completer = Completer();
  if (a < 0) {
    completer.completeError(ArgumentError("'a' must be positive."));
  } else {
    completer.complete(a * a * a);
  }
  return completer.future;
}

This example is similar to the named constructor solution above. It handles errors in addition completing the future in the normal way.

A note about blocking the UI

There is nothing about using a future that guarantees you won't block the UI (that is, the main isolate). Returning a future from your function simply tells Dart to schedule the task at the end of the event queue. If that task is intensive, it will still block the UI when the event loop schedules it to run.

If you have an intensive task that you want to run on another isolate, then you must spawn a new isolate to run it on. When the task completes on the other isolate, it will return a message as a future, which you can pass on as the result of your function.

Many of the standard Dart IO classes (like File or HttpClient) have methods that delegate the work to the system and thus don't do their intensive work on your UI thread. So the futures that these methods return are safe from blocking your UI.

See also

Poppo answered 12/10, 2019 at 8:33 Comment(1)
This is a superb answer! And I really appreciate the note about why await will still block the UI in the case of heavy workloads.Scintillant
K
7

@Fox32 has the correct answer addition to that we need to mention Type of the Completer otherwise we get exception

Exception received is type 'Future<dynamic>' is not a subtype of type 'FutureOr<List<Base>>

so initialisation of completer would become

var completer= new Completer<List<Base>>();

Klockau answered 24/4, 2018 at 6:46 Comment(1)
but when the control returns back from that function, how can we handle (receive) the object "List<Base>"?Kashakashden
B
1

Not exactly the answer for the given question, but sometimes we might want to await a closure:

    flagImage ??= await () async {
      ...
      final image = (await codec.getNextFrame()).image;
      return image;
    }();

I think it does create a future implicitly, even though we don't pass it anywhere.

Barrator answered 10/3, 2021 at 14:2 Comment(0)
K
0

Here a simple conditional Future example.

String? _data;
Future<String> load() async {
    // use preloaded data
    if (_data != null) return Future<String>.value(_data);
    // load data only once
    String data = await rootBundle.loadString('path_to_file');
    _data = data;
    return data;
}
Knighterrantry answered 12/12, 2022 at 19:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.