♪ [music] ♪
Welcome to the pilot episode of the Flutter Build Show
where we'll walk you through the raw mechanics
of building specific things in Flutter.
In this episode,
we're going to learn a lot about
including when and how you should write your own.
In Flutter, we love to say everything is a widget,
and it's true that most things developers touch are widgets.
In Flutter, any layout you can dream up is possible,
and off-the-shelf widgets can almost always do the job.
But "almost always" is different than "always."
That limitation comes from a little thing
called the Flutter's single-pass layout algorithm.
This means that when Flutter is turning your widgets into an actual layout,
no back-and-forth is allowed.
"But how can that be?" you ask.
Surely the column widget's
proves that Flutter does have this back-and-forth, right?
Flutter has to know how big the children widgets are
before it can place them.
Surely that constitutes back-and-forth during layout.
Well, functionality like this is why Flutter has so many different trees,
including the three most popularized ones:
widgets create the declarative API
that we all know and love;
elements are the translation layer
that hold it all together;
and
layout, painting, and semantics
from one frame to the next.
So what do
Well, a simplified day in the life of a
might look like this.
First, the
when its widget creates it with
Then during the rendering phase,
Flutter works its way through three critical methods:
performLayout,
paint,
The first
wherein the
figure out how big you're going to be.
If the original widget had children widgets,
then this
to determine their size.
This happens all the way down the render tree
until everything is sized.
Later on, Flutter calls
on all the
which combines their previously calculated size information
with whatever other parameters they have
to start making actual graphics calls.
Lastly, Flutter calls
which builds out the semantics tree,
which Flutter uses for accessibility.
Those are the three big methods in a
performLayout,paint,
and
Then things are stable,
but eventually, you call
meaning it's the dawn of a new frame.
New widgets with your new values
are slotted into the widget tree,
and Flutter calls
to route your new values
to the
Each
Only this time, it's not guaranteed
that Flutter will call each one
because depending on what changed,
cached values from the last frame may still be valid.
It's up to individual
which we'll see for ourselves later.
Unlike widgets,
RenderObjects
and live for as long as possible within your app,
often supporting many frames in one lifetime.
That's a high-level overview of
Sibling UI elements cannot typically influence each other,
but sometimes they have to,
which specialized
with specialized implementations of
RenderObjects
Rows and columns use just such specialized
but the rules are strict
and exposed to us through the well-known
and
What do you have to do if you need highly tailored layout rules
specific to your app?
Well, the answer might be to write your own
One scenario that requires a dedicated
is a common message bubble in chat apps like WhatsApp
where the
if it fits,
but wrapped down to an additional line
if it doesn't.
Flutter's single-pass layout algorithm
doesn't cleanly allow for this UI.
You could use
to make measurements after the first frame
and slot in that
but that would create UI flickers for this scenario
and would look terrible.
We need a custom
When we're done, it'll look like this.
Let's build it.
To start, I've created a fresh project
with
to spin up a bare project.
Let's fill our page with a
and a centered, sized
which will begin to house our vague chat app UI.
In that column, first add some padding around a text field,
and above that, our chat bubble,
called a
To help it look a little more like a chat bubble,
I'll decorate it with a helpful
and an
which will push it to the right.
Next, create that
we imagined in the text field
to quickly test our
against different values.
To link the two,
wrap our
Use our
and build our widget like normal.
Lastly, we'll ultimately need three parameters for this widget:
a
another
and a
We're now ready to work on the
The vast majority of widgets you've written in the past
likely extended stateless widget or stateful widget.
But this time, we're going with something else:
one of the descendants of
To figure out which one,
we need to think about the parameters
we want to pass our
One alternate option would have been to accept a
and a
This is how most widgets work, of course.
Lots of widgets have a
but it'll be tricky in our case
because we need to perform exact measurements
on individual characters and lines in the message.
That's tough to do if the raw string value
is buried deep within a
Plus, what would we do
if the user supplied an image or a stack
or something else wonky?
None of those values make any sense for what we're building,
which is why the appropriate constructor signature
is to accept a
a
and a second
and, of course, that
The appropriate parent class
when the widget in question does not have any children widgets
is called
Our
in its branch of the widget tree.
If this is your first time in the render tree,
you may be surprised to learn that
don't have
Instead, we need to supply two other methods:
createRenderObject
In
I'll add a stub that we'll finish later.
For now, this method will return
a
a name that really rolls off the tongue.
Okay, onto the
Again, there are different parent classes to choose from,
but the right one for our case is
This is the most common
when your UI element will fit in, well, a box.
The first thing we have to see to is our
and the parameters it will require.
Once those parameters are all saved to private variables,
let's create two
that we will use later.
The next thing to understand with
is how they think about their attributes.
Remember how widgets are lifeless configuration objects
and can never change?
Well, that's not how
RenderObjects
They can change their color, size, and shape
or anything else without missing a beat.
Technically, chameleons can only change one of those things,
but you get the idea.
For every attribute our
we need to write a special getter and setter.
The convention looks like this.
For the getter, simply proxy
to the private variable we set in our constructor.
For the setter,
first evaluate whether the items are the same.
If they are,
That's called a "guard statement"
where you check short circuit conditions at the top of a method
and return early.
That's how
After that guard statement, we know the value is new.
Here, assign the new value to the private variable.
We'll also need to update our
but remember that it took a
To help ourselves,
let's make
for both our core message and our
Later, we'll return to these magic setters
and update some flags.
But don't worry about that yet,
just remember that we'll be back here.
We need getters and setters for all of our attributes,
so let's add them quickly.
With the setters in place,
we can finish
in our widget class.
For
remember that widgets are immutable,
so if any values have changed,
the old widget is long gone,
and Flutter will call this method on its replacement.
This new widget has no pointer
to the original widget's
so Flutter passes it in
to
We can now make use of the setters we just wired up
to apply the new
It's up to the
to work out what, if anything, to do with those.
Okay, a lot of our code is in place,
but sadly, we still can't run the app and see anything.
That's because we have yet to implement
the three critical methods in our
performLayout,paint,
and
We'll start with
performLayout
determine the dimensions of whatever we're rendering
and save it to the
For
you should call
and then interrogate its size,
but that's not our scenario.
We don't have any children widgets.
We're a leaf node in the widget tree.
The upcoming sizing and measuring code
gets a little nitpicky.
It's somewhat zoomed in for our purposes here
learning about generic
I'm going to quickly breeze through it,
but know that the full thoroughly commented source code
is linked below.
To lay out text, call
and supply a maximum width,
which we can pull from
At this point, our
but we need more granular information,
like the length of the longest line
and the length of the last line.
To access this information,
call
which returns a list of line metrics objects.
We're close now.
We can set up a few variables
to save values for later
and then a little manipulation of that line data
pulls out the other info we need.
We can now save a helpful variable called
based on the longest line
and the number of lines.
We'll use this later.
You might guess what comes next.
Repeat the same process with our
and measure its size.
Now, we can compare whether or not those two widths
can squeeze into
which sets the all-important
Note to check for whether or not our message itself
has one line or more than one line.
Finally, remember that we owe Flutter a size here,
not just knowledge of whether or not
fits onto the last line of the message.
To save the actual size of the widget,
combine knowledge of whether or not
with the number of lines in the message itself
to make the right measurements.
Remember, this fully commented code is available below.
We have one more task
to make sure we didn't just calculate something larger
than our available constraints.
To do this, we'll use the
on box
and save those results to
There it is.
performLayout
We're ready to move on to the next critical method:
paint.
The first thing to know about
is that it's called after
so we should favor the
we set in
when making calculations.
paint
a
which we'll use to actually draw stuff;
and an
which is essentially our distance from the upper-left corner of the screen.
To draw the raw message itself,
we can return to our
and call
passing in
This is the first moment
our code has been capable of rendering anything,
so I'm going to save and run my app.
But I do have to tend to one detail I forgot earlier,
which is to set an initial string value on our
There it is.
Our text centered on the screen,
just like if we'd used a simple text widget.
So pretty.
But this is where things get fun.
Back in our
calculate a special
which adds the extra distance needed
to correctly place
in relation to the message text.
Finally, call
passing in that same
and our new
Save and run our app again.
Voilà!
Our widget is placing all the text correctly.
One thing I'm noticing is that the
like we wanted,
which is because I forgot to update
the
to use our
There.
That's better.
That said, there are a few other problems with our widget.
It doesn't yet correctly rebuild itself
or participate in Flutter's semantic system,
which is used for screen readers and other accessibility tools.
First, we'll handle semantics
in the third big
describeSemanticsConfiguration.
describeSemanticsConfiguration
that the
Semantics is a whole another topic in Flutter,
so for this, I'll only go as far as to say
I took inspiration
from a render paragraph's version of this method
when I came up with these lines.
That's how you write the three critical methods:
performLayout,paint,
and
However, there's one more detail we have to see to.
You see, Flutter calls each of those methods
whenever our widget or
but it doesn't always know when to call them later on.
For that, we need to get familiar
with each of those three methods' complementary methods.
markNeedsLayout,
markNeedsPaint,
and
are methods we call
when we've just done something that invalidates the old layout,
painting calls, or semantics configuration.
But what does that mean?
When and where should we call something, like
Well, the rules are actually very simple.
Think back to those magic setters we wrote in our
Every time we update a variable
that was used in
we call
If you update something that change the pixels the
call
Of course, if you change
the semantics meaning of your
call
Revisit what you wrote in each of the three main methods
and notice which of your class variables
influenced that method's output.
Then whenever you change those variables,
mark that work as stale
and in need of an update.
Now, back to our running app,
typing in new text reveals the final product at last.
One detail worth noting
is that
so you never need to call both of those.
That was a lot of code,
but we've reached the finished product.
At the outset, we faced a UI
that Flutter single-pass layout algorithm
couldn't elegantly handle,
but with the power of
learned how to write the necessary imperative code
that can juggle back and forth concerns like this
and efficiently lay out our app's chat messages.
This is a viable
though there is more you can add to this class
for a more robust implementation,
like supporting placing this in an intrinsic widget.
But that additional code is in the full source below.
In the next episode, we'll build on this knowledge
and explore how you'd implement dazzling effects
with a custom shader
if you didn't have the help of any packages.
Leave us a comment below
and let us know what you'd like to see
in future episodes of the Build Show.
For everything else on Flutter,
head to
♪ [music] ♪