Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter: Best Practices of Calling Async Code from UI

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

like image 205
Wonderjimmy Avatar asked Mar 06 '18 16:03

Wonderjimmy


2 Answers

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.

like image 118
Shady Aziza Avatar answered Sep 18 '22 16:09

Shady Aziza


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).

like image 35
Dan Field Avatar answered Sep 18 '22 16:09

Dan Field