27

I'm reading in user-provided input (in this case a zip code) from a TextField that I need to check against a database for validity. However, I need to make an asynchronous database query inside of the submit button's (a RaisedButton in this case) onPressed: () {} lambda function. In most programming languages, this is a fairly straightforward and simple task. The problem I'm running into in Flutter, however, is the fact that Future objects returned from asynchronous database queries can only be consumed by FutureBuilder objects which in turn only return Widget objects. I simply need a String returned that I can then use to either pass to a new route via a MaterialPageRoute object, or display an error to the user without changing routes. Is there any way to do this with Flutter? Returning a Widget is useless to me as I don't want to create a new Widget for display. I am using Flutter 0.3.2 and Dart 2.0.0

As a simplified example of where I need to call the database query:

@override
Widget build(Buildcontext context) {
    return new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
            new Container(
                padding: const EdgeInsets.all(16.0),
                child: new TextField(
                    keyboardType: TextInputType.number,
                    controller: _controller,
                    decoration: new InputDecoration(
                    hintText: 'Zip Code',
                ),
                onSubmitted: (string) {
                  return string;
                },
              ),
            ),
            new RaisedButton(
                onPressed: () {
                        // use regex to test against user input
                        if (_controller.text != null && _controller.text.isNotEmpty) {
                            RegExp zipCodeRegExp = new RegExp(r"^(\d{5})$");

                            // if the user input validates...
                            if (zipCodeRegExp.hasMatch(_controller.text)) {
                            zipCode = _controller.text;

                           // need to perform database query here and return a string, not a Widget

                            } else {
                               // an else condition here
                            }
                        } else {
                           // an else condition here
                        }
                    }
                }
            ),
        ],
    );
}

Perhaps I'm not following the "mantra" of Flutter? I appreciate your consideration and input on this.

Ad Astra
  • 599
  • 1
  • 4
  • 10

4 Answers4

40

FutureBuilder is just a convenient helper to get the widget tree rebuilt when a Future completes.

You can use

funcThatReturnsFuture().then((result) {
  print(result);
  setState(() {
    someVal = result;
  })
})

or

Future funcThatMakesAsyncCall() async {
  var result = await funcThatReturnsFuture();
  print(result);  
  setState(() {
    someVal = result;
  })
}

The main limitation is that you can't return the value directly to the caller without a Future, because there is no way to get back from async execution to sync execution.

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Yeah, I can appreciate that, but if I simply create a FutureBuilder object instance, that itself returns a widget. So how would one get the snapshot data from that without returning the Widget itself to be part of a parent Widget? – Ad Astra May 11 '18 at 18:22
  • If you don't want to build a widget, I don't see why you would want a builder (of whatever kind) – Günter Zöchbauer May 11 '18 at 18:24
  • Do I need to query synchronously instead of asynchronously in order to get a return type other than a Future? Would this be my solution for not wanting a Widget? – Ad Astra May 11 '18 at 18:38
  • You can't access servers over the network sync and if you could, this would block the UI until the response arrives. – Günter Zöchbauer May 11 '18 at 18:40
  • It's just a query to a local sqlite database, no network connectivity required for it. – Ad Astra May 11 '18 at 18:42
  • In this case it might work. I haven't used SQLite from Flutter. – Günter Zöchbauer May 11 '18 at 18:42
  • 1
    Ok, thanks for your help, Gunter. I appreciate it :) – Ad Astra May 11 '18 at 18:44
  • So I want to return something after getting object out of Future. How do I do that?? I am able to print but not able to return. So i declare variable outside the Future call and set the values. But since this is asynchronous, the value I had put while declaring gets returned. – Leena May 31 '20 at 17:13
  • You just can't. You need to read up on async execution. This is not Dart specific, this is async specific. Imagine a Future like a callback. The callback is called from the runtime looong after `funcThatMakesAsyncCall` has returned. If you instead return the Future, then outside code can wait for the callback being called. This is why async is contagious. https://dart.dev/codelabs/async-await, https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ might help for deeper understanding – Günter Zöchbauer May 31 '20 at 17:27
20

I've since figured this out (I believe this is what Günter was originally saying, but the fundamental reason why wasn't clear to me at the time). The only way to be able to consume a Future without creating a Widget object is by using the Future API. The Future API allows the parsing of a Future object as though it was an AsyncSnapshot object (which is where one would parse .data in a FutureBuilder builder: function). This can be performed on a returned Future object (which can use async with await). For example:

Future regionName = dbClient.getRegionNameFromZipCode(int.parse(zipCode)); <-- this database method getRegionNameFromZipCode returns a Future object and uses async and await

regionName.then((data) {
   String hZonesString = data[0]['hzone'];
   print(hZonesString);
}, onError: (e) {
     print(e);
   });

This is rather simple once you understand how the Future API can be leveraged, and it's intent vs. using FutureBuilder. Good to know for newbies of this language such as myself!

Ad Astra
  • 599
  • 1
  • 4
  • 10
3

A Future is just semantic sugar for a callback. Imagine you had:

void fetchName(void Function(String) callback);

void main() {
  fetchName((name) {
     print('Your name is: $name');
  });
}

There isn't a way to convert (or extract) name from fetchName. It doesn't exist until the callback is completed, and the callback may not be completed immediately (could be read from a database, like your example, or the network, etc).

One of the advantages of using FutureBuilder is it really helps make sense of asynchronous abstractions like Future (and StreamBuilder for Stream), and let you focus on writing (synchronous) builder code:

new FutureBuilder<String>(
  future: _calculation, // a Future<String> or null
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none: return new Text('Press button to start');
      case ConnectionState.waiting: return new Text('Awaiting result...');
      default:
        if (snapshot.hasError)
          return new Text('Error: ${snapshot.error}');
        else
          return new Text('Result: ${snapshot.data}');
    }
  },
)
matanlurey
  • 8,096
  • 3
  • 38
  • 46
  • It seems like the code inside the FutureBuilder declaration only gets executed when returned to a parent Widget. For instance, when I instantiate a FutureBuilder without a return (as in `return new FutureBuilder`), it doesn't actually execute that code. A test with a print() statement inside the builder: () method never executes. It's as if you have to then consume the Widget object. Am I missing something (there's a good chance I am)? – Ad Astra May 11 '18 at 18:29
0

you can create two methods, one to extract data which you put in a variable and another as like getter (In my case I need to access secure value with plugin flutter_secure_storage without using Futurebuilder)

mixin SecureStorageMethods {
 String keyValue;
 Future<String> _getFromSecureStorage(String key) async {
 keyValue = await slL<FlutterSecureStorage>().read(key: key);
 return keyValue;
}

String getSecureValue(String key) {
 _getFromSecureStorage(key);
 return keyValue;
}
}