I am developing a Flutter app but wondering what should I do when calling async code from UI - let's say the build method of a UI widget.
For example, my app connects Firebase through a service class, which uses Async-Await style to grab records from Firebase. Using await ensures my service class's method will complete records retrieval before returning to the UI side.
However, since the service class's method is marked as async, it returns a Future immediately to the calling UI widget and the UI code keeps running, before the service class's method is completed.
Yes I can write codes for the "then()" callback to handle the retrieved records, but what about other codes inside the widget that follow, and depend on the result of the async call? Does it mean I need to embed everything inside the "then()" clause and avoid adding any execution steps after a method call that returns a Future? Any suggested pattern for such usage model?
@override
Widget build(BuildContext context) {
RealtimeDatabase.getData() // my service class
.then((onValue) {
..... // do something after the async call is completed
}
..... // do something immediately before the async call is done
class RealtimeDatabase {
Future<String> getData() async {
DataSnapshot dataSnapshot = await FirebaseDatabase.instance.reference().orderByKey().once(); // will wait until completed
.....
.....
Sorry if my description here is lack of clarity, any advice is much welcome.
Jimmy
In Flutter there are widgets that can help you do this seamlessly (e.g, FutureBuilder
, StreamBuilder
) and you can control what to be rendered based on their resolution.
Example on FutureBuilder
:
Widget build(BuildContext context) {
return new FutureBuilder(
future: FirebaseDatabase.instance.reference().child("node"),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData? new Scaffold(
///start building your widget tree
):new CircularProgressIndicator(); ///load until snapshot.hasData resolves to true
},);
}
Example for StreamBuilder
:
class Database {
DatabaseReference _refProfile = FirebaseDatabase.instance.reference().child(
"profiles");
getProfiles() => _refProfile.onValue; }
.............
Widget build(BuildContext context) {
return new StreamBuilder<Event>(
stream: _database.getProfiles(), //_database = new Database()
builder: (BuildContext context, AsyncSnapshot<Event> event) {
return event.hasData?new Scaffold(
///build your widget tree
):new CircularProgressIndicator();
/// place holder
}
It is worth mentioning that
FutureBuilder
is better used when you want to fetch some data once and do not care of having consistent connection or tracking any changes in the data.
While StreamBuilder
, on the other hand, enables you to keep listening to the data and you can update the state of the UI according to any update in the data.
While I think the methods mentioned by @aziza are definitely the way to go, it's worth noting that you can "roll your own" as well.
So, for example, in initState()
, you could do something like this:
@override
void initState() {
super.initState();
_myItems = // some sane default
FirebaseDatabase.instance.reference().child("node")
.then((items) {
if (widget.mounted) setState(() { _myItems = items; }
});
}
Widget build(BuildContext context) {
return new Scaffold(
///start building your widget tree
);
}
It'd be preferable to use FutureBuilder
here, but this is another way to do it (even if it's not necessarily a best practice).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With