[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]
finally, this is back online