Skip to content

async coding with dart

source: https://www.youtube.com/playlist?list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2

1754901323.png

  1. isolates and event loops: https://www.youtube.com/watch?v=vl_AaCgudcY
  2. futures: https://www.youtube.com/watch?v=OTS-ap9_aXc
  3. streams: https://www.youtube.com/watch?v=nQBpOIHE4eE
  4. async/await
  5. generators

isolates and event loop

source: https://www.youtube.com/watch?v=vl_AaCgudcY

ANDREW BROGDON: Hey, everybody. I'm Andrew from the Flutter team, and welcome to the "Flutter in Focus" miniseries on a asynchronous programming in Dart. This is the first in a run of videos covering the ways Dart, despite being a single-threaded language, offers support for futures, streams, background work, and all the other things you need to write in a modern, asynchronous, and, in the case of Flutter, reactive way. Since this is the first video in the series, I'm going to start all the way down at the foundation of what makes asynchrony possible with Dart, and that's the isolate. 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. In a lot of other languages like C++, you could have multiple threads sharing the same memory and running whatever code they want. In Dart, though, each thread is in its own isolate with its own memory and it just processes events. More on that in a minute. Many Dart apps run all their code in a single isolate, but you can have more than one if you need it. If you have a computation to perform that's so enormous it could cause you to drop frames if it were run in the main isolate, you can use Isolate.spawn() or Flutter's compute function (5), both of which create a separate isolate to do the number crunching, leaving your main one free to rebuild and render the widget tree in the meantime. That new isolate will get its own event loop and its own memory, which the original isolate, even though it's the parent of this new one, isn't allowed to access. That's the source of the name isolate. These little spaces are kept isolated from one another. In fact, the only way they can work together is by passing messages back and forth. One isolate will send a message to the other, and that receiving isolate process is the message using its event loop. This lack of shared memory may sound kind of strict, especially if you're coming from a language like Java or C++, but it has some key benefits for Dart coders. For example, memory allocation and garbage collection in an isolate don't require locking. There's only one thread, so if it's not busy, you know the memory's not being mutated. That works out really well for Flutter apps, which sometimes need to build up and tear down a bunch of widgets really quickly. All right. So that's a basic introduction to isolates. Now let's dig into what really makes async code possible, the event loop. Imagine the life of an app stretched out on a timeline (6). Here you start, there you stop, and in between, there are all these little events, like I/O from the disk, or finger taps from the user-- all kinds of stuff. Your app can't predict when these events will happen or in what order, and it has to handle all of them with a single thread that never blocks, so it runs an event loop (7). Simple as can be. It grabs the oldest event from the event queue, processes it, goes back for the next one, processes that one, and so on until the event queue is empty. The whole time the app is running-- you're tapping on the screen, things are downloading, a timer goes off-- that event loop is just going around and around, processing those events one at a time. Whenever there's a break in the action, the thread just kind of hangs out, waiting for the next event. It can trigger the garbage collector, get some coffee, whatever. All of the high level APIs we're used to for asynchronous programming-- futures, streams, async and await-- they're all built on and around this simple loop. For example, say you have a button that initiates a network request, like this one. (1) You run your app and Flutter builds the button and puts it on screen, then it waits. The event loop just sort of idles, waiting for the next thing to process. Other events not related to the button might come in and get handled while the button just sits there waiting for the user to tap on it. Eventually they do, and a tap event enters the queue (8). That event gets picked up for processing, Flutter looks at it, and the rendering system says, hey, those coordinates match the RaisedButton, so Flutter executes the onPressed function. That code initiates a network request, which returns a Future and registers a completion handler for the future by using then. And that's it. The loop is finished processing that tap event and it's discarded. Now, onPressed was a property on RaisedButton, and here we're talking about a callback for a future. But both of those techniques are doing basically the same thing. They're both a way to tell Flutter, hey, later on, you might see a particular type of event come in. When you do, please execute this piece of code. onPressed is waiting for a tap and the future is waiting for network data. But from Dart's perspective, those are both just events in the queue. And that's how asynchronous coding works in Dart. futures, streams, async, and await-- these APIs are all just ways for you to tell Dart's event loop, here's some code. Please run it later. If we look back at the code example, you can now see exactly how it's broken up into blocks for particular events. There's the initial build (2), the tap event (3), and the network response event (4). Once you get used to working with async code, you'll start recognizing these patterns all over the place. And understanding the event loop is going to help as we move on to the higher level APIs. All right. So that's a quick look at isolates, the event loop, and the foundation of async coding in Dart. In our next video, we're going to talk about futures, a simple API you can use to take advantage of these capabilities without a ton of code. In the meantime, leave a comment below if you have a question, and come see us at flutter.io. [MUSIC PLAYING] Hey, if you enjoyed that video, try these others, or subscribe to the Flutter channel. It's Google's new portable UI toolkit. There's a button around here somewhere.

  1. 1754942468.png
  2. 1754943313.png
  3. 1754943406.png
  4. 1754943419.png
  5. 1755115353.png
  6. 1755115652.png
  7. 1755115680.png
  8. 1755115926.png

Threads

In a typical Linux system with a 16-thread CPU running Chrome with 16 tabs, Flutter development, and Neovim, you'd likely see several hundred to over a thousand kernel threads being managed simultaneously. ~ Claude

The program in memory (in memory since instantiation: HDD -> RAM/memory), there might be the section in the memory that defines the current state of the app: eg- how buttons responds, how things look in the ui. By design, this part of the memory can only be accessed by one thread (this is how dart isolates are designed). So one thread has to deal with or coordinate the entire reactive behavior. This thread is the app's main event loop.

futures

source: https://www.youtube.com/watch?v=OTS-ap9_aXc

ANDREW BROGDON: Hey, everybody, and welcome to the second video in our Flutter in Focus series on asynchronous coding patterns in Dart. Today we're going to cover one of the most basic APIs Dart has for async-- Futures. Most modern languages have some kind of support for asynchronous programming. Many offer a futures API, and some call them Promises. And for the most part, Dart's Futures are very similar to those found in other languages. I like to think of them as little gift boxes for data. Somebody hands you one, and it starts off closed. Then a little while later, it pops open. And inside, there's either a value or an error. So those are the three states a Future can be in. First, the box is closed. We call that uncompleted. Then the box opens, and it's completed with a value or completed with an error. Most of the code you're about to see revolves around dealing with these three states. (1) You know, one of your functions gets a Future, it needs to decide, OK, what do I do if the box isn't open yet? What do I do when it opens later and I have a value? And what about an error? And so on. So you're to see that one, two, three pattern a lot. You might also remember this guy(2) from our previous video about the Dart Event Loop. A good thing to know about Futures is they're really just an API built to make using the Event Loop easier. The Dart code you write is executed by a single thread. The whole time your app is running, that one little thread just keeps going around, picking up events from the Event Queue and processing them. Futures work with the Event Loop to simplify things. Say you have some code for a download button. (3) The user taps, and it starts downloading a picture of a cupcake or something. Well, first, the tap event occurs. (4) The Event Loop gets it, and your tap handler gets called. It uses the http library to make a request, and it gets a future in return. So now you've got your little box, right? It starts off closed, so your code uses then to register a callback for when it opens. (5) Then you wait. (6) Maybe some other events come in. The user does some stuff, and your little box just sits there while the Event Loop keeps going around. Eventually, data for the image arrives. (7) And the https library says, great. I've got this Future right here. It puts the data in the box and pops it open, which triggers your callback. Now that little piece of code executes and displays the image. (8) Throughout that process, your code never had to touch the Event Loop directly. Didn't care what else was going on, what other events came in. All it needed to do was get the Future from the https library and then say what it was going to do when the future completed. If I were a better coder, I probably would have added a code in case it completed with an error. But this series is a safe space.

  1. 1755117451.png
  2. 1755121988.png
  3. 1754950584.png
  4. 1754950810.png
  5. 1754950884.png
  6. 1754950925.png
  7. 1754950970.png
  8. 1754951025.png

All right-- first question. How to get an instance of a Future? Most of the time, you're probably not going to be creating Futures directly. That's because many of the common async tasks already have libraries that generate Futures for you. Like network communication returns a Future (1). Accessing shared preferences returns a Future (2). But there are also constructors you can use. The simplest is the default, which takes a function and returns a Future with the same type (3). Then later, it runs the function asynchronously and uses the return value to complete the Future. Let me add a couple print statements here to make clear the asynchronous part. (4) Now when I run this, you can see the entire main method finishes before the function I gave to the Future constructor. That's because the Future constructor just returns an uncompleted Future at first. It says, here's this box. You hold onto that for now, and later, I'll go run your function and put some data in there for you. If you already know the value for the future, you can use the Future.value named constructor. (5) The Future still completes asynchronously, though. I've used this one when building services that use caching. Sometimes you've already got the value you need, so you can just pop it right in there. Future.value also has a counterpart for completing with an error, by the way. It's called Future.error, and it works essentially the same way. But it takes an error object and an optional stack trace. (6) The constructor I probably use the most, though, is Future.delayed. (7) It works just like the default one, only it waits for a specified length of time before running the function and completing the Future. I use this one all the time when creating mock network services for testing. If I need to make sure my little loading spinner is displaying right and then goes away, somewhere, there's a delayed Future helping me out. All right. So that's where Futures come from.

  1. 1754951837.png
  2. 1754951867.png
  3. 1754951894.png
  4. 1754951936.png
  5. 1754952045.png
  6. 1754952098.png
  7. 1754952126.png

Now let's talk about how to use them. As I mentioned earlier, it's mostly about accounting for the three states a Future can be in-- uncompleted, completed with a value, or completed with an error. Here's a Future.delayed creating a Future that will complete three seconds later with a value of 100. (1) Now when I execute this, main runs from top to bottom, creates the Future, and prints waiting for a value. That whole time, the Future is uncompleted. It won't complete for another three seconds. So if I want to use that value, I'll use then. (2) This is an instance method on each Future that you can use to register a callback for when the Future completes with a value. You give it a function that takes a single parameter matching the type of the Future. And then once the Future completes with a value, your function executes with that value. So if I run this, I still get waiting for a value first. And then three seconds example, though, what happens if that initial Future doesn't complete with a later, my callback executes and prints the value. In addition, then returns a Future of its own matching the return value of whatever function you give it. (3) So if you have a couple asynchronous calls you need to make, you can chain them together, even if they have different return types. value?

  1. 1754952192.png
  2. 1754952236.png
  3. 1754952325.png

Back to our first example (1), though, What if it completes with an error? then expects a value. We need a way to register another callback in case of an error. And you could do that with catchError. (2) Catcherror works just like then, only it takes an error instead of a value, and it executes if the Future completes with an error. Just like then, it returns a Future of its own. So you can build a whole chain of thens and catch errors and thens and catch errors that wait on one another. You can even give it a test method (3) to check the error before invoking the callback. You can have multiple catch error methods this way, each one checking for a different kind of error. Now that we've gotten this far, hopefully you can see what I mean about how the three states of a Future are often reflected by the structure the code. There are three blocks here (2). The first creates an uncompleted Future. Then there's a function to call when the Future completes with a value and another if it completes with an error.

  1. 1754952409.png
  2. 1754952497.png
  3. 1755127345.png

I do have one more method to show you, though, which is whencomplete (1). You can use this to execute a method when the future is completed, no matter whether it's with a value or an error. It's like the finally block in a try catch finally. There's code executed if everything goes right, code for an error, and then code that runs no matter what. So that's how you create Futures and a bit about how you can use their values.

  1. 1755127616.png

Now let's talk putting them to work in Flutter. This will probably be the least complicated section of this video. Let me show you why. Say you have a network service that's going to return some JSON, and you want to display it. You could create a stateful widget that will create the Future, check for completion or error, call set state, and generally handle all that wiring manually. Or you can use FutureBuilder (1). It's a widget that comes with the Flutter SDK. You give it a Future and a builder method, and it will automatically rebuild its children when the Future completes. It does that by calling its builder method, which takes a context and a snapshot of the current state of the Future. You can check the snapshot (2) to see if the Future completed with an error and report it. Otherwise, you can check the has data property to see if it completed with a value. (3) And if not, you know you're still waiting. (4) So you can output something for that as well. Even in Flutter code, you can see how those three states keep popping up-- uncompleted, completed with value, and completed with error. All right. That's all we've got for this video, but there are more coming in the series. Next up, we'll be talking about streams. They're a lot like Futures, in that they can either provide values or errors. But where Futures just give you one and stop, streams keep right on going. So be on the lookout for that and head to dart.dev and flutter.dev for more info on Dart and Flutter. [MUSIC PLAYING] Hey, if you enjoyed that video, try these others. Or subscribe to the Flutter channel. It's Google's new portable UI toolkit. There's a button around here somewhere.

  1. 1754952681.png
  2. 1754952732.png
  3. 1754952770.png
  4. 1754952796.png

Using dart to view the result (stdout) of a particular command:

import 'dart:io';
import 'package:flutter/material.dart';

void main() async {
  runApp(ProcessResultViewer());
}

class ProcessResultViewer extends StatelessWidget {
  ProcessResultViewer({super.key})
    // : processResult = Process.run('find', ['/tmp']);
    : processResult = justADelayer(); // (1)

  final Future<ProcessResult> processResult;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Process Result Viewer")),
        body: FutureBuilder(
          future: processResult,
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              return Text('There was a error');
            } else if (snapshot.hasData) {
              return SelectableText(snapshot.data?.stdout.toString() ?? "");
            } else {
              return Text("Processing...");
            }
          },
        ),
      ),
    );
  }
}

Future<ProcessResult> justADelayer() async {
  return Future.delayed(Duration(seconds: 10), () {
    return Process.run('find', ['/tmp']);
  });
}

  1. Doing this so that we can see what the FutureBuilder displays when there is no data.
import 'dart:io';

void main() {
  // List all files in the current directory in UNIX-like systems.
  var result = Process.run('ls', ['-l']);
  result.then((processResult) {
    print(processResult.stdout);
  });
  print('This will be printed before the result of ls -l');
}

We know that our dart program will be a single thread that runs an event loop. Any serious dart code that goes beyond printing text will work with the event loop. One of the APIs that make working with the event loop is Future. Most of the time we won't need to create Future, we can get Futures from other packages which have already taken care of the implementation.

Process.run('cmd', ['option', 'value' ]) was the first thing that came in my mind.

All the code we write around Future are based on the three states that a Future can be in:

  1. Uncompleted
  2. Completed with a value
  3. Completed with an error

streams

source: https://www.youtube.com/watch?v=nQBpOIHE4eE

Hey, everybody, and welcome to the third video in our "Flutter in Focus" series on asynchronous coding patterns in Dart. In this episode, I'm going to cover one of the fundamentals of reactive programming, streams.

If you saw our previous video on futures, you may remember that each Future represents a single value, either an error or data that it delivers asynchronously. Streams work similarly, only instead of a single thing, they can deliver zero or more values and errors over time. If you think about the way a single value relates to an iterator of the same type, that's how a future relates to a stream (1). Just like with futures, the key is deciding in advance here's what to do when 1. a piece of data is ready, 2. when there's an error, and 3. when the stream completes. And the Dart event loop is still running the show. If you're using files.openRead method to read data from a file, for example, it returns a stream. Chunks of data are read from disk and arrive at the event loop. dart:io looks at them and says, "Ah, I've got somebody waiting for this," adds the data to the stream, and it pops out in your app's code. When another piece of data arrives, in it goes and out it comes. Timer-based streams, streaming data from a network socket-- they work with the event loop too using clock and network events.

  1. 1755076550.png Within the body of an iterator (eg: for loop) the iteration count is available. Likewise in the body of a stream, you get the particular type of the stream. For Stream its int.

Okay, let's talk about how to work with data provided by a stream. Say I have a class that will give me a stream that kicks out a new integer once per second-- one, two, three, four, five (1). I can use the listen method to subscribe to the stream (2). I give it a function, and every time a new value is emitted by the stream, my function gets called and prints it (3). That's how listen works.

  1. 1755082968.png
  2. 1755082999.png
  3. 1755083039.png

One important thing to note is that, by default, streams are set up for single subscription. They hold onto their values until someone subscribes, and they only allow a single listener for their entire lifespan. If you try to listen to one twice, you'll get an exception (1).

  1. 1755083086.png

Fortunately, Dart also offers broadcast streams (1). You can use the asBroadcastStream method to make a broadcast stream from a single subscription one. They work the same as single subscription streams, but they can have multiple listeners. And if nobody's listening when a piece of data is ready, it gets tossed out.

  1. 1755083895.png

Let's go back to that first listen call though because there are a couple more things to talk about (1). I mentioned earlier that streams can produce errors just like futures can-- by adding an onError method you can catch and process any errors (2). There's also a cancelOnError property (3) that's true by default, but can be set to false to keep the subscription going even after an error. And there's an onDone method you can use to execute some code when the stream is finished sending data, such as when a file has been completely read (4). With all four of those properties combined, you can be ready in advance for whatever happens (5).

  1. 1755083933.png
  2. 1755198977.png
  3. 1755083962.png
  4. 1755084040.png
  5. 1755084081.png

Before moving on to the next section, I should mention that the little subscription object (1) that's so far gone unnoticed has some useful methods of its own. You can use it to pause, resume and even cancel the flow of data (2). Okay, so that's a quick look at how you can use listen to subscribe to a stream and receive data events. Now we get to talk about what makes streams really cool: manipulating them.

  1. 1755084131.png
  2. 1755199115.png

Once you've got data in a stream, there are a lot of operations that suddenly become fluent and elegant. Let's go back to that number stream from earlier (1). I can use a method called map (2) to take each value from the stream and convert it, on the fly, into something else. I give map a function to do the conversion, and it returns a new stream, typed to match the return value of my function. Instead of a stream of ints, I now have a stream of strings. I can throw a listen call on the end, give it the print function and now I'm printing strings directly off the stream, asynchronously, as they arrive (3).

  1. 1755199564.png
  2. 1755084221.png
  3. 1755084346.png

There's a ton of methods you can chain up like this (1). If I only want to print the even numbers, for example, I can use where to filter the stream. I give it a test function that returns a Boolean for each element, and it returns a new stream that only includes values that pass that test. distinct is another good one. If I have an app that uses a ReduxStore, that store emits new app state objects in an onChange stream (2). I can use map (3) to convert that stream of state objects to a stream of ViewModels for one particular part of my app. Then I can use the distinct method to get a stream that filters out consecutive identical values— in case the store kicks out a change that doesn't affect the subset of data in MyViewModel. Then I can listen and update my UI whenever I get a new ViewModel (5).

  1. 1755084394.png
  2. 1755200109.png
  3. 1755200155.png
  4. 1755084472.png
  5. 1755084521.png

This is a command line which expects file contents to be provided via its stdin (via pipes), but if not you can enter the data line by line until you press <Ctrl-D>

import 'dart:convert';
import 'dart:io';

void main() {
  var subscription;
  if (stdin.hasTerminal) {
    stdin.lineMode = false;
    stdin.echoMode = true;
    List<int> input = [];
    subscription = stdin.listen((data) {
      if (data.contains(4)) {
        subscription.cancel();
        print("\n${utf8.decode(input)}");
      } else {
        input.add(data[0]);
      }
    });
  } else { // if there is no terminal
    stdin.map((data) => utf8.decode(data)).listen((data) {
      print(data);
    });
  }
}

There are a bunch of additional methods built into Dart that can use to shape and modify your streams. Plus, when you're ready for even more advanced stuff, there's the Async package, maintained by the Dart team and available on Pub. It has classes that can merge two streams together, cache results and perform other types of stream-based wizardry.

Alright, there's one more advanced topic that deserves a mention here, and that's how to create streams of your own. Just like with futures, most of the time, you're going to be working with streams created for you by network libraries, file libraries, state management and so on. But you can make your own as well using a stream controller. Let's go back to that number creator we'd been using so far (1). Here's the actual code for it (2). As you can see, it keeps a running count (3), and it uses a timer to increment that count each second (4). The interesting bit though is the stream controller (5). A stream controller creates a brand new stream from scratch and gives you access to both ends of it. There's the stream end (6) itself where data arrives. We've been using that one throughout this video, and there's the sink (7), which is where new data gets added to the stream. NumberCreator here uses both of them. When the timer goes off, it adds the latest count to the controller's sink, and then it exposes the controller's stream with a public property so other objects can subscribe to it.

  1. 1755085323.png
  2. 1755085346.png
  3. 1755085387.png
  4. 1755085420.png
  5. 1755085449.png
  6. 1755085500.png
  7. 1755085527.png

Now that we've covered creating, manipulating, and listening to streams, let's talk about how to put them to work building widgets in Flutter. If you saw the previous video on futures, you may remember FutureBuilder (1). You give it a future and a builder method, and it builds widgets based on the state of the future. For streams, there's a similar widget called StreamBuilder (2). Give it a stream, like the one from NumberCreator and a builder method (3), and it will rebuild its children whenever a new value is emitted by the stream. The snapshot parameter (4) is an async snapshot just like with FutureBuilder. You can check its connectionState property to see if the stream hasn't yet sent any data, or if it's completely finished (5). And you can use the hasError property (6) to see if the latest value is an error and handle data values as well (7). The main thing is just to make sure your builder knows how to handle all the possible states of the stream. Once you've got that, it can react to whatever the stream does. Okay, that's all we've got for this video, but there are more coming in the series. Next up, we'll be talking about Async and Await. They're two key words Dart offers to help you keep your asynchronous code tight and easy to read. So be on the lookout for that, and head to Dart.dev and Flutter.dev for more info on Dart and Flutter. ♪ (music) ♪ Hey, if you enjoyed that video, try these others or subscribe to the Flutter channel, it's Google's new portable UI toolkit. There's a button around here somewhere. ♪ (music) ♪

  1. 1755085587.png
  2. 1755085618.png
  3. 1755086069.png
  4. 1755085644.png
  5. 1755085914.png
  6. 1755086148.png
  7. 1755086184.png

The Future delivers a single value asynchronously. Asynchronously here means it is not proessed by the main thread which is the event loop. All asynchronous operation end up as another thread distinct from the event loop thread.

Just some commands

Finding process ID of main flutter thread
ps aux | grep "flutter" | grep -v 'grep' | grep -v 'lsp' | grep -F -- '-d linux'

async/await

source: https://www.youtube.com/watch?v=SmTCmDMi4BY

Hey everybody, and welcome to the fourth video in our Flutter in Focus series on asynchronous coding in Dart. In this episode, I'm going to show you how to use Dart's async and await keywords to simplify your code. A lot of languages have async and await in their syntax, and the first time I saw them I remember being weirded out. I knew you tagged a function as async, the return type changed, and somewhere in the middle there was a break where it would wait. But at the time it just seemed magical and weird. I have some good news for you all watching this, though. If you've seen the first few videos in this series, you already know the things you need to fully understand async and await. And that's because, at the end of the day, they're really just an alternate syntax for using futures and streams that can help you write cleaner, more readable code. Let's start with a simple example (1). Say I have a class that represents some processed data. I can give it a string and it'll do some business logic that I need done. I also have a method that'll load an id value from disk and another that'll fetch some network data that I can use with my class. (2) Network and File I/O are asynchronous operations, so they return futures. I'd like to write a single method that will put these pieces together. First, it should load an id from disk, then use that id to make a network call, then make a ProcessedData object with the result (3). With Dart's futures API, I can use then to chain callbacks together so that the completed value of the first future becomes the parameter for the next callback, and so on. This technique was covered earlier in the series, and it works great. The code isn't as readable as it would be if this were all synchronous, though. If it weren't for the futures, that code could be written like this (4), right? Make some calls, in order, no big whoop. Well, the big deal about async and await is that you can have code that looks like this, and uses futures. First, add the async keyword, just before the opening brace (5). This is just a way of telling Dart, "Hey! I plan to use the await keyword in here." Speaking of which, next up is placing the await keyword in front of each future the function needs to wait for. It can't call fetchNetworkData without the id from loadFromDisk, so there needs to be an await there. And it can't create a ProcessedData object without the data from the network. So, there needs to be an await there (6). The last change is to make the return type a future (7). That might look weird at first, because the return statement on this function just uses a regular ProcessedData object. But before createData here completes, it has to wait on two futures. That means it's going to start executing, then stop, and wait for a disk event, then keep going, then stop and wait for a network event. And only after that can it provide a value. So when createData starts running and hits that first await, right then it returns a future to the calling function. It says, "Hey! Looks like I'm going to have to wait on some stuff. Here's this empty box, you hold onto that, and when I'm done waiting for this disk in the network, I'll call the return statement and put some data in there for you. Go put it in a FutureBuilder or something." And that's how this works. Before moving on, let's take a quick look back at the event loop, and how it works with both versions of the code you just saw (8). We started with this one which uses the futures API. And one of the nice things about the futures API is that you can easily see how the code is broken down for the events involved (9). First, the function starts running and calls in to loadFromDisk (10). Then it waits (11). It waits for some data from the disk to arrive at the event loop (12). That completes the future returned by loadFromDisk. So, the callback from the first event statement is invoked and a network request goes out (13). Then createData waits again (14). It waits for some network data to arrive at the event loop. That completes the future returned by fetchNetworkData, so the second callback is invoked (15), some process data is created, and now the future that was returned by this createData is completed with that value (16). Now, let's do the same thing again, using the async/await version of the code (17). Spoiler Alert! It's the exact same process. Before, we could use the calls to then to imagine how the code is broken down, event by event (18). Here, you can do the same thing by breaking the code after each await expression. So we get this nice line-by-line progression. Let's run it. createData starts executing and hits that first await (19). At that point, it returns its own future to the calling function, and invokes loadFromDisk. Then, just like before, it waits for that File I/O event from the disk (20). That completes the future returned by loadFromDisk, which means createData is done awaiting on it and can go on to the rest of the code (21). Next, it calls fetchNetworkData, and waits again (22). Eventually, the network data arrives at the event loop, that completes the future return by fetchNetworkData, and so createData is free to move on again. It creates and returns an instance of ProcessedData (23), which completes the future (24) that createData gave to its caller way back at the beginning. As you can see, in both cases, the same event loop controls the action, and the same futures are involved. The only real change is that with async/await the function is smaller and looks more like synchronous code. Hopefully, at this point, some of you are thinking, "Hey, I watched a couple of the other videos in this series and you said futures could complete either with data or an error. What's up with async/await and errors?" The answer is that async and await also help make your error handling look more like what it would be with synchronous code. If we go back to that first example based on the futures API (25), error handling code might look like this (26). It uses catchError to test and respond to errors, and, when complete, to execute a callback at the very end, whether there's an error or not. With async and await (27), on the other hand, rather than using additional callbacks, you can use try-catch (28). Inside a function tagged with the async keyword, try-catch blocks will handle asynchronous errors the same way they handle synchronous ones in normal functions. You can use the on and catch keywords to trap specific types of exceptions and finally will execute its code block at the end as you would expect. Okay, there's one last thing to cover, and that's how to use await with a for loop to process data from a stream. This is a much less common use case for the await keyword, but it is something you can do. Say I have a function that can take in a list of numbers and add them all up (29). That's pretty straightforward, right? I can just use a for loop to iterate over the values. What if I wanted this function to take a stream of numbers instead (30), add them up asynchronously as they arrive, and then, when the stream is finished, return that sum? Just like with futures, async/await helps me make that happen without changing the basic structure of the code. First, I tag the function as async, then I change the return type to a future, and then I add the await keyword in front of for, and I'm done! (31) Just like with futures, the await keyword is separating my function into the parts that execute before and after waiting on events. First, it starts executing and gets all the way to that await (32). Then it returns its future to the calling function and waits for a piece of data to arrive. When it does, the loop executes once to process that piece of data and then stops and waits for the next one (33). Maybe the app runs off and does some other things, garbage collects, whatever. Eventually, though, another piece of data arrives and the loop goes around again. This keeps happening until the stream is finished and closes. When that happens, the function exits the loop and executes its return statement (34). That completes the future that getTotal here gave to its caller way back at the beginning (35). One important thing to keep in mind when using await for is that you should only use it with streams you know are going to complete. If you try to use this with a stream of clicks coming from an HTML button, for example, that stream lasts as long as the button's around, which means your loop could just keep right on waiting. Alright. That's all we have for this video, but there's one more left in the series, and we'll be talking about generator functions. These are functions that can return multiple values over time, creating a stream of data on the fly. So, be on the lookout for that, and head to dart.dev and flutter.dev for more info on Dart and Flutter. ♪ (music) ♪ Hey! If you enjoyed that video, try these others. Or, subscribe to the Flutter channel. It's Google's new portable UI toolkit. There's a button around here somewhere. ♪ (music) ♪

  1. 1755068762.png
  2. 1755068866.png
  3. 1755068937.png
  4. 1755068985.png
  5. 1755072332.png
  6. 1755072398.png
  7. 1755072418.png
  8. 1755072504.png
  9. 1755072538.png
  10. 1755072563.png
  11. 1755072611.png
  12. 1755072802.png
  13. 1755072753.png
  14. 1755072949.png
  15. 1755073004.png
  16. 1755073042.png
  17. 1755073077.png
  18. 1755073122.png
  19. 1755073157.png
  20. 1755073190.png
  21. 1755073219.png
  22. 1755073243.png
  23. 1755073274.png
  24. 1755073304.png
  25. 1755073372.png
  26. 1755073399.png
  27. 1755073430.png
  28. 1755073457.png
  29. 1755073519.png
  30. 1755073559.png
  31. 1755075569.png
  32. 1755075614.png
  33. 1755075649.png
  34. 1755075690.png
  35. 1755075718.png

Comments