Asynchronous Programming is a way for us to be able to carry out time-consuming tasks such as loading an image to show in the app. In this series, I will try to explain the concept of Asynchronous Programming and how it works in Dart in a very simple way, especially if you are a beginner.

I will break down the topics we need to cover in order to understand this concept into various parts. So, without any further ado, let’s jump straight into the Part-1 of this series.

Topics that I will cover here in this part:

  1. What is Asynchronous Programming?
  2. How Asynchronous programming is performed in case of Android?
  3. How Asynchronous programming is performed in Flutter and how it is different from Android?
  4. Future, a basic API from Dart team for performing tasks asynchronously

What is Asynchronous Programming?

When we write code there are lines of code which takes a very short amount of time to execute. For example, a print statement for printing something on the console takes just a fraction of seconds to complete. But sometimes we need to write code that might take more time. For example, if we wanted to load a large image from the internet, it definitely would take more time than just printing something onto the console or performing some mathematical operations. This is because there is a lot of data that we are trying to transfer from some server. So, the larger the amount of data, the longer the time it takes for it to load.

Now, just take a look at the piece of code below:

void main() {
print("Light Yagami");
loadDeathNotePosterSync();
print("Ryuk, the Shinigami");}

The first statement is a simple print statement, so it will get executed immediately. Now, the second statement is trying to load an image for Death Note (Anime) poster, maybe an HD image, so it might take some time to get completed and because as you know a program is executed from top to bottom, our third statement which is just trying to print ‘Ryuk, the Shinigami’ onto the console can’t execute until the second statement gets executed. So, it’s only once the image is loaded that the computer will run the very last statement. Now, this behavior is what we call Synchronous.

Now how would it look like if we do it in an Asynchronous way?

void main() {
print("Light Yagami");
loadDeathNotePosterAsync();
print("Ryuk, the Shinigami");}

Statement 1 will execute, as usual, no changes there, but things will get a little interesting in Statement 2. Here let’s assume that now we are loading the image from the server asynchronously. In this case, while the image is loading we can already execute Statement 3 and we don’t have to wait for Statement 2 to get its execution completed first.

So, this is the difference between Synchronous and Asynchronous pattern. Now, why is this Asynchronous pattern useful?

Let’s take a real-life example to understand this.

Imagine you go to work and your boss wants you to collect the Passport Numbers of your customers. So, you have to call all the customers one by one and ask them their passport numbers. You maintain a checklist and you’re going from top to bottom one by one calling up each customer. Now it’s not as simple as it seems to be. Very few people remember their passport numbers off the top of their head. So, this can take anywhere from a couple of seconds to some minutes depending upon how well they’ve stashed away their passport.

Now if a customer is taking some time, finding his/her passport then you are not being productive at that time. But once he/she has given you the passport number you can finally spring into action again and you can go and call the next customer and this goes on and on until you reach the final customer in that list.

Many of you must have realized that there can be a different approach to do this task. You could in fact have sent the individual e-mails off to all your customers asking for their passport numbers. Now afterward this leaves you free to do any other tasks that you need to do making you much productive and as the replies come in you can respond to them so you no longer have to be there on the phone waiting for their response.

I hope from the above example you are able to relate how asynchronous programming can make your code more efficient.

How Asynchronous programming is performed in case of Android?

If you are an Android Developer, then most probably you are already familiar with how asynchronous operations are performed on Android. But for the ones who don’t have any clue, let me cover this in brief for you:

  1. When we open an Android app then a process is created by default.
  2. Attached to this process, we have the Main UI thread.
  3. Whenever we interact with the application, we do it with the main UI thread. Within the Main UI thread, we perform small operations such as UI interactions like a button click, checking the checkbox or maybe some animations. We can also perform mathematical and logical operations within the main UI thread.
  4. All of these operations require a very small amount of time and remember in the Main UI thread we always have to perform tasks that require a smaller amount of time to complete.
  5. In Android, if we want to perform some heavy operation such as a network request to fetch some JSON response or downloading/uploading of a file, we have to create a separate thread known as Worker or Background Thread, which runs parallel to the main thread. This worker thread takes care of performing long-running tasks such as image loading, heavy database queries, etc.

Since Android (Kotlin/Java) supports Multi-threading, so we can have two threads running parallel to each other. In this way, asynchronous programming works in case of Android.

How Asynchronous programming is performed in Flutter and how it is different from Android?

Now comes the section you all are waiting for: Asynchronous programming in Dart.

Unlike Java or Kotlin, Dart is single-threaded i.e. in Flutter, we can’t have two threads running parallel to each other. We can only have the main UI thread but not a separate worker or background thread.

Question: If so, then how to deal with long-running tasks in case of Flutter?

Answer: In case of Flutter, all these kind of operations are performed on the main UI thread itself.

Now you might be having some sort of doubt or confusion. If we perform these heavy tasks on the main UI thread, then the app might freeze or even crash. In Flutter we have some APIs like Future, Async, Await. Using these APIs we can perform long-running tasks on the main UI thread itself but without blocking the UI in a very elegant way. I will talk about async and await in some other part. For now, let’s move on to the Future.

Futures in Dart

Before heading towards Futures, let’s cover some basic terms:

Isolates: An isolate is what all dart code runs in. It’s like a little space on the machine with its own private chunk of memory and a single thread running an Event loop.

Event loop: Some of you may already know about the event loop if you’ve written UI code. An event loop ensures that events such as mouse clicks, button taps are handled one at a time.

What an event loop basically does is:

  1. Take an event from the event queue and handles it.
  2. Repeats Step 1 unless the queue becomes empty.

Now imagine the life of your app stretched on a timeline. When you open your app, it’s the start of the timeline; When you close your app, it’s the end of the timeline; and in between, there are all these events (like finger taps from the user, IO from the disk). The app can’t predict when these events will happen or in what order. Also, it has to handle all these events with a single thread that never blocks. So. it runs an event loop, which takes the oldest event from the event queue, processes it then moves towards the next event and so on until the event queue is empty. All the async APIs like Future, Async, Await are built on and around this event loop.

Now I want to grab your attention towards the following code snippet:

import 'dart:math';
void main() {
//Statement 1
print("Light Yagami");

//Statement 2
String result = "Pending";

//Statement 3
var myFuture = Future.delayed(Duration(seconds: 3), () {
Random rand = Random();
int res = rand.nextInt(3);
if (res == 0)
throw Exception();
else if (res == 1)
result = "Resource not available";
else
result = "Image Loaded Successfully";
return "Result: $result";
});

//Statement 4
myFuture.then((result) {
print(result);
});

//Statement 5
myFuture.catchError((error){
print('Caught $error');
});

//Statement 6
print("Ryuk");

//Statement 7
print("Result: $result");
}

Before diving directly into the code, let’s first try to make some sense about what Future actually is.

You can think of a Future as a simple gift box containing some data. So, a Future (our Gift box)can be in one of these 3 states: Closed, Popped with some value or Popped with an error.

You can perform actions based on these 3 states. As in what to do when the future is uncompleted (box is closed); what action to perform when it is completed with data (box opened with some value) and of course how to deal when Future is completed with error (box opened with an error). You even have an option to run a piece of code when the Future is completed either with a value or an error just like the Finally block in case of Java.

Now let’s go through the code one statement at a time. You can copy-paste the above code snippet in your Dart IDE and run it or you can go to Dartpad. Statement 1 and 2 will get executed usually.

In Statement 3, I have tried to simulate a network call for loading an image from a server. Just assume it will take 3 seconds in the whole process of making the network call and getting the response.

Now what I’m actually doing in Statement 3 is, I am using the delayed method of Future class which takes a Duration object and a callback (anonymous function) as arguments and returns a Future instance. Now when compiler moves to Statement 3, it waits for 3 seconds as I have mentioned in the Duration object and then executes the callback in which a random number is generated between 0 to 2 and based on that number we’re either setting the result value or throwing an error. Also, note that Statement 3 performs an asynchronous operation i.e. the code written below it can execute without depending on whether the Future instance in Statement 3 has been completed or not.

Now then() function in Statement 4 takes a callback as an argument and this callback will be executed when myFuture is completed with value and this callback takes that value as an argument. In the above example, we’re getting the result as an argument to the callback and we’re just printing the result.

Similarly, catchError() function in Statement 5 takes a callback as an argument and this gets executed when myFuture is completed with some error. In the above example, if the value of res comes to be 0 then we’re throwing an exception and in the callback, we’re just printing the exception.

Now Statement 6 and Statement 7 will be executed normally. Also notice that Statement 7 will always print Result: Pending on the console. Why? Most of you may have guessed it right, because of the asynchronous pattern of coding. Notice that in Statement 2 we’re setting the value of the result to “Pending”. Now future in Statement 3 will be completed after 3 seconds either with a value or with an error but it will not block the following code. Also, callback sin Statement 4 and Statement 5 will get executed upon completion of myFuture. Therefore, the result will be having the value “Pending” at the time Statement 7 is executed.

Bonus: Some of you might be wondering why the UI doesn’t get froze or the app gets crashed if we are performing async operations on the main UI thread itself. Well, that’s just the beauty of the Event loop and how Dart functions.

Consider a single screen Flutter app having a single button and a Text widget. On click of the button, we’re making an HTTP call to the server and displaying the response in the Text Widget.

Here is what is happening with the event loop:

  1. The UI is rendered onto the screen.
  2. Now the event loop is waiting for any event to occur.
  3. Suppose user taps on the button. The event loop gets it and the onPressed callback of the button widget will get executed.
  4. Now the http.get() method is executed which will make a network request for the required data, and this will return a Future (Remember? Our sweet little gift box).
  5. Now when this future gets completed, i.e. our gift box opens with a value or error, it’s nothing but an event for the event loop. The event loop gets this event and executes the handler of that event i.e. then() or catchError().

So, it doesn’t matter for the event loop if it’s getting a tap event or a future completed event. In the end, it’s just an event and the event loop job is to take the event and execute the handler or callback. I hope now all of this is making some sense to you.

That’s it for this part. I will cover some more topics in the next part. Till then Goodbye!

References:

Official Dart Documentation

Smartherd Flutter tutorials

Flutter in Focus playlist

London App Brewery Flutter Course