How Flutter renders Widgets

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

finally, this is back online

👍︎︎ 3 👤︎︎ u/mannprerak 📅︎︎ Nov 23 2019 🗫︎ replies
Captions
[MUSIC PLAYING] [APPLAUSE] MATT SULLIVAN: Ni hao. Thank you, everyone, for turning up at nearly 5:00 after a long day of technical sessions. We're very grateful to have you here to talk about how Flutter renders widgets. My name is Matt Sullivan. ANDREW FITZ GIBBON: My name's Andrew Fitzgibbon. MATT SULLIVAN: And we're going to spend the next half an hour talking about how Flutter works under the hood. What we talk about today isn't really necessary for your day to day job by writing Flutter, but it will give you a solid understanding and help you to build performant Flutter apps. ANDREW FITZ GIBBON: And so, just to remind you, in case you missed it from the keynote this morning, Flutter is Google's UI tool kit for building beautiful applications on the web, mobile, and desktop. Now, as you can see, you can do very interesting things with Flutter, and so we are going to do something very simple. We have an app that has some text in the middle of the screen. That's all we have. MATT SULLIVAN: This is like the simplest Flutter app ever. ANDREW FITZ GIBBON: This is the very simplest app ever. MATT SULLIVAN: Two widgets. ANDREW FITZ GIBBON: We have two widgets. That's it. And so, when you first learn Flutter, one of the things you learn is that everything is a widget, and what we do under the hood is we build this widget tree. And in this case, we have a Center and we have a text. But let's make it a little bit more interesting, just barely more interesting, and make the background white. We do that by adding a container at the top. MATT SULLIVAN: Three widgets. ANDREW FITZ GIBBON: We now have three widgets. And so our widget tree looks like this. We have a container at the top, a Center, and a RichText. So I think, Matt, it's undeniably true that everything in Flutter is a widget. MATT SULLIVAN: It's undeniably true. ANDREW FITZ GIBBON: It's undeniably true. MATT SULLIVAN: It's uncategorizable. I just made that word up. ANDREW FITZ GIBBON: You just made up that word. That's great. MATT SULLIVAN: Well, in reality, that's not necessarily the case, because if you dive into our documentation, you will see statements like this, where it says, "A widget is an immutable description of part of a user interface." So looking into the box, you think about this and you go, well, immutability and UIs don't go hand in hand because UIs are not immutable. Different parts of your UI mutate and change. Your UI is constantly in flux. New widgets come in and go. Pieces are swapped in and out. But if Widgets are immutable, how does Flutter manage the state of your UI? How does it represent change? ANDREW FITZ GIBBON: That's a great question, Matt. MATT SULLIVAN: It is a great question, isn't it? I'm going to answer it. ANDREW FITZ GIBBON: Thank you. MATT SULLIVAN: So in reality, what Flutter has is Flutter has three concepts for this. It doesn't have one tree of widget. It actually has three. It has the tree of widgets, the widget tree. It has a tree of elements. And finally, it has a third tree, which is a tree of RenderObjects. And these three different concepts, we combine together to make the rendering of a UI as performant as we possibly can. So these are new concepts, maybe, for some of you, so let's look at the Flutter documentation again and see what it has to say about these three things. So we've already seen that a widget is immutable, and so that kind of makes sense because you are working with a declarative framework. You declare what your UI is going to look like, and so you configure it, and you configure it with widgets. And so a widget describes the configuration of an element. So what does that mean for an element? Well, in fact, what an element is, it's an instantiation of those widgets. It's the mutable piece of the tree, for want of a better word. It's the thing that manages updating and changing of the UI. It controls everything. You can think of it as managing the lifecycle of widgets. And thirdly, we have a RenderObject, which is an object in the render tree. And basically what that means is you have a tree of RenderObjects, and that is what is going to lay out and paint your UI. When Flutter draws your UI, it does not look at a tree of widgets. It looks at a tree of RenderObjects, which controls all of these things. So let's look at this from a slightly different perspective. We have configure, lifecycle, and paint. And so how does this map to UIs? Well, on the configuration side of things, configure, lifecycle, and paint. Maybe I should click faster. So when you configure your UI, basically this is you writing widgets. You are assigning properties and values to the components, and you're doing it through an API, and that's what ends up as your widget tree. Then you have the lifecycle, and this is what is going to manage the whole lifecycle of your UI. It's going to work out what components exist in your UI hierarchy, and it's going to manage the relationship between these elements and handle their mutation and morphing. And then finally, you have to paint your UI. You actually have to draw these things onscreen, and that's where RenderObjects come in. Each component knows how to paint itself, but importantly, it also provides constraints for its children. So for example, when you saw a Center widget and you have some text, what's happening on the rendering side is that that Center is constraining where that text can paint. So these three concepts, configure, lifecycle, and paint, they map really nicely onto the separation of concerns that Flutter has between widgets, elements, and RenderObjects. And so we have our widget tree, which does-- this is where you define. This is where the configuration happens. We have our element, which is going to handle our lifecycle. And we have the RenderObject, which is going to lay out and it's going to paint. And so you combine all these things together into these three trees, which have these three different separations of concerns. So there's a whole bunch of theory here, and you might be asking yourself, well, this is great, but this sounds more complicated than I need to render a UI. Why are you creating three things when one might be perfectly acceptable? So let's start to jump in at a slightly lower level and see an example of this, and we're going to play with-- ANDREW FITZ GIBBON: Yes. MATT SULLIVAN: Padding. ANDREW FITZ GIBBON: So now that Matt has read us the documentation-- thank you, Matt-- we're going to look at some real code and some real examples here. So take, for example, Padding. It's a very simple widget. All it does is add some space around things. So here's the code for it. We have some Padding, and it has some text within it, and it adds some space. Very simple. Let's remind ourselves of the responsibilities of the three trees. Widgets configure, elements manage, and RenderObjects paint. In the case of Padding, our widget has two responsibilities. It needs to know how much Padding and it needs to know what is its child. The element here really doesn't do anything much other than manage the widget and manage the RenderObject. And so, speaking of the RenderObject, it also doesn't have to do much. All it has to do is it's responsible for layout and size, and in the case of Padding, it just needs to remember enough size for the child plus its Padding. And that's it. That's all our three trees do. And so by building your UI declaratively like this, you can actually get very performant apps because the Flutter framework doesn't have to destroy very much. It can reuse a lot of different components. And so, that still felt a little bit like theory. We still haven't really seen code. I showed some code, but it hasn't really-- MATT SULLIVAN: No. So again, let's drop down another level, and let's actually look at the source code from the Flutter framework for when it creates a Flutter app. And what, again, is the most simplest app that we can come up with? ANDREW FITZ GIBBON: That same app that we showed right up at the beginning, nothing but text. MATT SULLIVAN: And no Center Object. ANDREW FITZ GIBBON: There's no Center. There's just a single widget. And so we have our RichText. When Flutter starts the app, it calls the runApp function. It takes that widget that you give the runApp and it puts it at the root of the tree. Now we have our widget tree. It has a single widget at the root, nothing more. The next thing the Flutter framework does is ask the widget to create the RenderObject. Sorry. It creates the RenderObject element. MATT SULLIVAN: Yes. ANDREW FITZ GIBBON: Yes. MATT SULLIVAN: The element first, the RenderObject later. ANDREW FITZ GIBBON: That's right. We'll get to the RenderObject later. MATT SULLIVAN: OK. OK. ANDREW FITZ GIBBON: So Flutter asks the widget to create the element. This is called the LeafRenderObjectElement in this case. That's where I got confused. MATT SULLIVAN: And it's a leaf element because it's at the leaf of the tree. Again, our trees are only one level deep with one widget. ANDREW FITZ GIBBON: Yes. The next thing Flutter does is ask the element to create the RenderObject. When it does that, the widget then passes all of the configuration required to paint whatever that widget is. And so in this case, it's text, and so we have a number of text-related properties. And now we have our widget trees. We have our RichText widget, our LeafRenderObjectElement, which does not need any children, and our Render Paragraph, which actually does the painting. And so, while I was talking about that, it felt kind of weird to me because sometimes text changes. You want to change the label of a button or you want to change some other aspect of it. And so, Matt, what happens when something changes in our widget? MATT SULLIVAN: So again, you're probably still asking yourself, why do we have three trees for this? We seem to be doing a lot of work to get one thing on the screen. And so the magic really starts to happen is when things change. And what we're going to do is we're going to effect change in a Flutter app in the simplest way possible, which means we're going to call runApp twice in the same app. ANDREW FITZ GIBBON: RunApp twice? MATT SULLIVAN: Yes, runApp twice. You would never call runApp twice-- at least, I couldn't think of a good reason for doing it-- inside a Flutter app. But what this does is, by calling runApp, it's going to take that first widget and it's going to replace it with a new widget, and we're going to examine what happens to our super simple tree in this use case. So we have our app, and it's going to go from this to this, which is basically runApp creating the first RichText widget, and then runApp creating a new RichText widget with different text. As you can see when we run this, the second piece of text is shown. How does that get rendered on the screen? So here are our three trees, widget, element, and RenderObject. Now the first thing that happens is runApp is called and it pops our new widget onto the root. And at this point, Flutter's asking itself, what am I going to do in this case? I am replacing one widget with another widget. I've already done all this work to create these elements and RenderObjects. I'm going to need to work out what I can reuse, if anything. And what it's going to do is it's going to call in the widget, the can update method, and this is very, very simple. All it's saying is, when I'm comparing two widgets, are the runtime types of both widgets the same? In this case, they're both RichText, so yes. And do the keys of these widgets match? And we're not using keys at the moment, so that doesn't matter. So we have two widgets, both of the same run type, and Flutter goes, aha. I can get rid of my old widget and I can now pop it back into these hierarchy of trees. So what happens next is the element goes, well, hey, you've just changed the widget that I point to so the configuration may be different, so I need to do something. And so what happens at this point is element will call update RenderObject, which looks very similar to create RenderObject, except that what it's doing is it's taking the existing RenderObject and it's updating the values inside it. So our render paragraph is updated, the text is updated, and the next time Flutter paints the UI using our RenderObject tree, it's now going to paint the new values. But notice what's happened here is we've swapped in one immutable widget for another immutable widget, and we haven't created or destroyed anything else. We've done a simple update. So that seems like we've gotten some performance gains, from that, which is nice. But having a widget tree of level one, or depth one, seems like a super simple example, so what happens when we have multiple widgets in the tree? ANDREW FITZ GIBBON: Right. So if we remember actually to our first app, we had two widgets. We had our text centered in the middle of the app. And so now we have two widgets-- we have Center and text-- and so our widget tree looks like this. Flutter is going to go through each of these widgets in turn, and it's going to start at the Center, and it's going to ask the Center widget, what is your element? In this case, the element is a SingleChildRenderObject. It's a bit of a mouthful, but it means it has a single child. Pretty simple. It then asks that widget to create the RenderObject. In this case, the RenderPositionedBox only has to know where on the screen to put the child, and that's it. That's all it does for Center. And then it drops down to RichText. And again, we have our LeafRenderObjectElement, and Flutter knows, in this case, that the last element it created needs a single child. And so it gives a reference to the LeafRenderObjectElement-- again, a mouthful, but that's OK-- up to the single child element. And then, as you'd expect, it creates the render paragraph RenderObject. So once again, I feel myself wanting something to change. And so, Matt, what happens with multiple widgets when something changes? MATT SULLIVAN: Well, so far you've learned that we have difficulty naming element classes. ANDREW FITZ GIBBON: Yes. MATT SULLIVAN: When we have leaf single element object child element. What we're going to do now is we're going to do exactly the same trick as we did before. We're going to take our app, and we are going to call-- we're going to take our widget tree and we're going to call runApp twice. So in both cases, we have Center and we have some RichText under it. Only when we call runApp a second time, we're going to change the properties of RichText. So again, we have two widgets with different properties at the leaf nodes. So this is going to start to seem familiar to you. We now instantiate the new widget tree. So we have Center and we have RichText. Remember, these two central widgets that you see are different. Widgets are immutable, and we created one and now we've created a second. And so what happens is Flutter goes, OK. Two Center widgets. They have the same runtime. I can probably reuse that. These two RichTexts also have the same runtime, so maybe I can reuse other parts of the tree. And that's what it does. So it blows away our old widgets and it pops our new widget tree, and it links in our single child element to Center. At this point, it will call update. But remember, because Center doesn't really have any properties other than its child, what happens here is that RenderPositionedBox doesn't change. Our single child element doesn't change. So we've popped in a new one, but at this point, our render tree and our element tree have stayed the same. We now wire in RichText. We're going to call update RenderObject in that, and what will happen is our render paragraph will be updated. So looking at this, again, it's a simple example, but you can start to see that the reason we have these three concepts is because it helps us optimize the amount of work we need to do to go from one UI state to an-- we don't throw everything away. We try to reuse as much as we can. And so this will go for when you have multiple children, we will do the same thing. You might see an example of that in a second. And when you have deep trees or broad trees and you're changing different pieces of it, we will run through the same process and we will try to get from this state of your render tree to the next state as quickly as we can so we can maintain those frames per second and we have to do the least amount of work to get there. So again, this has been a little bit of theory on that, so to round it all off, what we can do is we can look at a demo. And so, Fitz, what are we going to demo? ANDREW FITZ GIBBON: Yes, so I didn't walk away from that for no reason. I actually came over here to show you some actual code. I know things have been very in the slides. We haven't seen very much real things happening, so I want to look at that right now. And so here is another simple app. It looks very similar to the simple app that we've been working with. We have some text and an image. At the very bottom, we have a button. When I click on that button, the text and the image change from Hello Flutter to Hello Dart and back again, however many times do I want to do that. The code for this app is also very simple. At the top with our runApp, we have a widgets app, and the widget app has a single stateful element. Now, this stateful element also doesn't do much. It has a button and it has two trees that it switches between when we click the button. And you can see here that these two trees are almost identical. We have our RichText, and we have an image, and we have some space in between them just to make things look nice. Now, what we remember from the talk is that, when we replace some widgets with new widgets of the same type, we expect the RenderObjects to stay the same. Now, the way we can do that here is use the Dart developer tools. So I'm going to open those up, and the Dart developer tools are a set of browser-based tools for debugging and analyzing Flutter and Dart apps. And so you can see here, on the left, we have our widget tree, and this is almost exactly what we'd expect. At the root, we have our widgets app, and that's what we called runApp with was the widgets app. And then all the way down onto our RichText, our sized box, which we use for spacing, and an image. OK. Matt, can you do us a favor? MATT SULLIVAN: Sure. ANDREW FITZ GIBBON: I would like you to take some notes. MATT SULLIVAN: You want me take notes now. OK. ANDREW FITZ GIBBON: I would like you to take some notes. MATT SULLIVAN: I'm going to use some very, very sophisticated technology. ANDREW FITZ GIBBON: You're going to use an app that you wrote just for this? MATT SULLIVAN: I'm going to use this. ANDREW FITZ GIBBON: Oh, some paper. OK. I'm not familiar with that. MATT SULLIVAN: And-- I remember it. I'm going to use this. ANDREW FITZ GIBBON: Oh, great. MATT SULLIVAN: So what notes would you like me to take? ANDREW FITZ GIBBON: I would like you to note the instance IDs for our RenderObjects. MATT SULLIVAN: You want me to note the instance IDs for the RenderObject. OK. ANDREW FITZ GIBBON: Yes. So you'll see down here, for our RenderObjects, there's this series of numbers and letters, and that indicates the exact instantiation of that object in the render tree. OK. So each one of our widgets has a RenderObject, as we saw earlier. So we have the one for the RichText, the one for the sized box, and all the way at the bottom, we have the one for our image. OK. And so you can see here, you can also look at all the properties for the widgets, too. So you see here in our RichText, we have Hello Flutter, and that's what we'd expect because that's what's showing in the app. So I'm going to click our switch tree button, and now we have Hello Dart with the new image. And I will refresh the tree, go back and look at that, and we can confirm that everything's changed because our text property in the RichText now says Hello Dart. OK. And let's look at the RenderObject instance IDs again. Matt, how does that compare to our notes? MATT SULLIVAN: Which is identical. ANDREW FITZ GIBBON: What's that? MATT SULLIVAN: It's the same. ANDREW FITZ GIBBON: Oh, great. Excellent. MATT SULLIVAN: You sound surprised. ANDREW FITZ GIBBON: So our RenderObject instance IDs are the same for the RichText, the sized box, and all the way at the bottom again, the image, right? MATT SULLIVAN: Yep, all the same. ANDREW FITZ GIBBON: Perfect. OK. But seeing that nothing changed isn't very exciting. I'm going go to the code now, and we used sized box for some spacing in between our text and the image, and again, just to make things look nice. But we can also use Padding. So I'm going to change the code to have Padding, and I'm going to save that and click on switch tree. Because of Flutter's hot reload, I don't have to relaunch the app at all. I can just have it running here. And when I go back to the Dart developer tools and I click Refresh, in fact, I can see that it's the new app because our widget tree has changed to Padding. Great. OK. Let's look at-- I'm going to click Refresh again and look at our RenderObjects again. Matt, is that the same instance ID for RichText? MATT SULLIVAN: Yes. ANDREW FITZ GIBBON: Excellent. Is this the same-- that's not even the same RenderObject for Padding, is it? MATT SULLIVAN: No, that's a different RenderObject. And that's not surprising because you have taken a sized box and you have taken a Padding widget, and they're not the same runtime type. So you don't even know-- Flutter didn't even know what type of RenderObject it was going to create, so that's why it's had to destroy it and rebuild the element and the RenderObject for that widget. ANDREW FITZ GIBBON: Right. Exactly. So the Padding RenderObject has changed, but we expect that to happen because it's a completely different widget. And our image is still the same RenderObject. The interesting thing about the image RenderObject is that, even though the image itself is changing, and this feels like something that might be expensive because it's a new image. But in fact it's using the exact same RenderObject and Flutter is just telling it to render a different image file. OK. So now I'm going to switch tree again and refresh our widget tree. So we've seen now that the sized box has come back. Our RenderObject for the RichText is still the same one, but let's look at the sized box one. Matt, how does this compare to the previous ID we had for sized box? MATT SULLIVAN: That's a completely different RenderObject. ANDREW FITZ GIBBON: It's a completely different one. Yeah. And so what we've seen here is that Flutter took our Padding, and then, when it was trying to replace it with a sized box, it determined it didn't need Padding at all anymore. It didn't need Padding. It didn't need whatever its element is. It doesn't need whatever its RenderObject is, and it just threw it all away. And then it created the ones for sized box. But that's OK. It's OK that it did that because, basically, nothing else in the app had to change. All of the other RenderObjects, all of the other elements stayed exactly the same. So let's switch back to the slides and wrap it up. MATT SULLIVAN: And so we showed some fairly simple examples, and in reality, when you build a Flutter app, you will have hundreds and hundreds of widgets in several, several, several layers of depth across a broad spectrum. And rebuilding that entire tree is super, super expensive, which is why Flutter adopts this method of using these three concepts to minimize the amount of processing it needs to do to make these change. So let's wrap ourselves up? No, let's wrap this up, and remind ourselves of the responsibilities of the three trees. Widgets are there to configure your UI. That is what you are writing. You are declaratively defining what your UI is going to look like. Elements are there to make sure everything plays well together, and that your widgets, when they change, are changed out, things will be updated accordingly. And RenderObject exist to paint things to the screen. And using these three trees is why we are very happy with the performance that we get out of Flutter apps. Thank you very much. [APPLAUSE] [MUSIC PLAYING]
Info
Channel: Flutter
Views: 77,736
Rating: 4.916883 out of 5
Keywords: How Flutter renders Widgets, how Flutter works, what you can build with Flutter, apps, app development, app developer, Google Developer Days, build better apps, how to build better apps, GDD, GDD China, coding an app, Flutter, widget of the week, google, google developers, flutter 101, get started with flutter, flutter apps, build apps with flutter, Andrew Fitz Gibbon, Matt Sullivan, GDS: Yes;
Id: 996ZgFRENMs
Channel Id: undefined
Length: 26min 50sec (1610 seconds)
Published: Fri Nov 15 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.