Better Asynchronous Operations
Greg Perry
Posted on April 12, 2024
Better Asynchronous Operations
A slick built-in means to handle Asynchronous Operations
I’ve written up an example app in the GitHub repository called, inheritedwidget_state_mixin_example. It initially highlighted the Mixin, inheritedwidget_state_mixin, as it provides a built-in InheritedWidget to your State object for more efficient interface rebuilds. It has its own article called, The InheritedWidget Mixin, discussing its use and its features.
However, the example app used another Mixin as well. One which provided another built-in feature to your State object. This time, it’s a FutureBuilder Widget. That’s what this article will talk about. If you’re not interested, at least this article will demonstrate using a Mixin to you, and a code walk through on how the FutureBuilder Widget works. The screenshot below is of that Mixin where a FutureBuilder Widget resides in the State object’s build() function.
Using the Mixin, FutureBuilderStateMixin, a FutureBuilder Widget is integrated into your State object to better handle any Asynchronous operations that need to be done before the State object can display its content. Akin to the State class’ initState() function, this Mixin provides the State class with the initAsync() function as a slick and easy approach to complete such operations. I use this approach instead of executing asynchronous tasks even before calling the runApp() function as many other developers do.
Future<bool> initAsync() {}
In the first video below, the example app is started up in a simulator. The splash screen that appears is merely a sleepy kitten rolling over in the center of a black background. It runs for about ten seconds. The first screenshot below is that of the State class using the Mixin. You can see how that 10-second time is specified in its initAsync() function. The Splash screen itself is returned by the Mixin’s function, onSplashScreen:
Widget? onSplashScreen(BuildContext context)=> null;
Of course, your Splash screen could be much more elaborate than that. For example, it would have your company logo taking up the whole screen with some spectacular animation. Or nothing at all, as you see in the second video above, in which case it defaults to the appropriate ‘spinner indicator’ depending on the platform.
However, note how modular this approach is. For example, a standard procedure is to open all the databases needed at the beginning of your app. Therefore, opening them in the app’s first State object makes for a logical location. No? ‘Everything in its place’ as it were. You can dictate whether the app continues or stops right there if anything fails at startup. If there are further databases or other asynchronous operations necessary, you can perform those only when necessary in other screens (in other State objects) further on in the app and not before. This saves on resources.
Initiate Your Future
In the screenshot above, you saw highlighted the Mixin implemented to the State class, _MyAppState. Its initAsync() function runs for ten seconds emulating asynchronous tasks running to their conclusion. Note, when using this Mixin, you use the buildF() function instead of the build() function to define the interface.
The first screenshot below is of the Mixin itself. You can see the buildF() and onSplashScreen() functions. The build() function is already implemented running the initAsync() function. The Mixin continues in the second screenshot. There, you can see the initAsync() defaults to true and is actually called once in the runAsync() function with the flag, _ranAsync, set to true. After all, once open, you don’t want to open your databases again for example.
Another Future
The example app is relatively simple, but there are a number HTTP GET requests initiated by user interaction accessing some public API’s. Frankly, these API operations can take time to complete. Such API calls must also fail gracefully as well as wait patiently til completion. All this is provided in the Mixin, FutureBuilderStateMixin.
The screenshot below is of the code involved in each ‘image request’ you see completed in the video. You’re seeing twelve StatefulWidgets displaying their unique images. Each is making an HTTP Get request. Their State class uses the Mixin, FutureBuilderStateMixin, and as such will call their corresponding initAsync() function to complete such tasks.
Further, as you see in the video, the user has the option to tap on an individual image causing a new picture to appear. With every tap, the routine above calls its appropriate API, and while we wait, the appropriate spinner appears.
Four Futures
Below are four screenshots depicting the four categories of animals displayed in this app. Each is a separate class calling their particular API and all are a subclass of the State class, ImageAPIState:
It is the State class, ImageAPIState, that implements the Mixin, FutureBuilderStateMixin. *The Mixin appears in the second screenshot below. There, you can see the FutureBuilder Widget implemented in the **build() function. This means that each of the four classes above need not have a **build() function — their **build*() function is already defined for them.
Build Your Future
Again, it is the buildF() function where you now define the interface. In the first screenshot below, the State class, ImageAPIState, implements its buildF() function. A Card widget is used to contain the requested image. Note, if an error occurs during the Asynchronous operation, you may wish to handle such errors using the accompanying onAsyncError() function.
In the second screenshot below, you see where the buildF() function is called in the Mixin. If you want to know more about how the Mixin and its FutureBuilder Widget works, the TL;DR section below performs a code walkthrough.
One Future Once
If you recall, I had stated when those twelve StatefulWidgets display their animal images, they call their corresponding initAsync() function to complete such tasks. Well, I lied.
Instead of implementing the initAsync() function, their asynchronous procedures instead run in their corresponding runAsync() function. That’s so they will run again and again. Unlike opening databases just once, this app wants to make repeated API calls again and again. However, if these calls were in the traditional initAsync() function, no amount of tapping would result in new images — see the second screenshot and video below. Instead, they must be in the runAsync() function.
Your Future Platform
I switched out the platform indicator, UniversalPlatform.isIOS, with bool, true, just to show how funny the CupertinoActivityIndicator Widget looks on an Android phone emulator (see video below). That means, however, when running the example app on an iPhone, you’ll get that spinner instead.
Download the example app and place some breakpoints here and there and see how it can work for you.
Cheers.
TL;DR
Let’s walk through the series of function calls that occur in this Mixin’s FutureBuilder Widget. This will give you an idea of how this Mixin works as well as how a FutureBuilder Widget works. As you know, in the first screenshot below, this example app’s initAsync() function (seen in the second screenshot) is called in this Mixin’s runAsync() function.
Whether already completed or still running to completion (for at least 10 seconds), the Future object, _future, is then passed to the FutureBuilder Widget in the first screenshot below. Note, this FutureBuilder is dealing with a bool value and initially assigns the value, false, to its parameter, initialData.
A Stateful Future
A FutureBuilder Widget is a StatefulWidget. It requires a Future (already running to completion or already completed) of type T and a Widget builder function called in its State object’s build() function:
Widget build(BuildContext context)=> widget.builder(context,_snapshot);
The first screenshot below is of the FutureBuilder Widget with those two required parameters indicated. The second screenshot below is the _subcribe() function where the FutureBuilder Widget defines that Future object’s THEN clause. What’s in that clause will run after the Future object completes its asynchronous task. Note, a setState() function is in that clause routine. This tells you, when the Future object completes, the State object’s build() function is called again. Keep that in mind.
Of course, because a FutureBuilder Widget is a StatefulWidget, it has a State object. That State object will run its initState() function before running its build() function. That initState() function is displayed in the first screenshot below. Note, the AsyncSnapshot class object, _snapshot. Before it’s referenced in the build() function (the second screenshot below) it’s first defined in the initState() function. The _subcribe() function is where the Future object’s THEN clause is defined and also called. Stay with me.
A FutureBuilder Widget requires a Future object already running or already completed because, if not, it will run again and again with every subsequent FurturBuilder Widget call as described in its documentation:
The [future] must have been obtained earlier,…If the [future] is created at the same time as the [FutureBuilder], then every time the [FutureBuilder]’s parent is rebuilt, the asynchronous task will be restarted. — async.dart
Wait For It
Further on, in the _subcribe() function, after defining the Future object’s THEN clause, a new AsyncSnapshot class object (_snapshot) is created with a ‘connectionState’ value of, ConnectionState.waiting. See the first screenshot below. The Future object is not yet completed, and so the ‘builder’ is first called in the State object’s build() function with that _snapshot object, and it has the ‘connectionState’ value of, ConnectionState.waiting. See the second screenshot below.
The builder, in this example app, is called, _futureBuilder. It is displayed in the first screenshot below. It tests for snapshot.connectionState == ConnectionState.done, and if it’s not, continues to call the onSplashScreen() function. After that, if there’s still no Widget provided, the appropriate spinner is instead returned in the FutureBuilder Widget’s build() function. Still with me?
It’s Your Future
If all goes as intended, the next time the FutureBuilder’s build() function is called again, that Future object will have completed calling the State object’s setState() function and creating a new AsyncSnapshot class object (_snapshot) with a ‘connectionState’ value of, *ConnectionState.done. *All this is done in that THEN clause. See the screenshot below.
Widget build(BuildContext context)=> widget.builder(context,_snapshot);
Build Again
With the setState() function call, the State object’s build() function is called again. Of course, that means the example app’s Widget builder, _futureBuilder, is called again. The screenshot below of this function depicts what happens next. With the following two conditions true,snapshot.connectionState == ConnectionState.done
andsnapshot.hasData,the buildF() function is called.
In the second screenshot above, we see the buildF() function defined in the example app. It returns the StatefulWidget, HomePage. The screenshot below is the State class, HomePageState. It uses the buildIn() function instead because it has the built-in InheritedWidget. However, that's another story.
Posted on April 12, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.