Hi, I'm Chet Haase, an engineer on the
Android team at Google.
Today I'm going to do another show in a series I like to
call "You're Doing It Wrong."
Now, the demo you're going to see is not code that I would
like you to copy and paste.
In fact, it is code that I would like you to
not copy and paste.
Don't do what I'm doing today.
Instead, it's a hacked up demo to show an egregious example
of something that I have seen commonly in other
applications.
And usually, the effect is more subtle.
And being subtle, it takes a long time for me to track down
the problem.
But it all boils down to one single problem that a lot of
applications share, which is, don't call Request Layout
during layout.
And let's take a look at the demo.
And then I'll talk a little bit about why this is a really
bad thing for your application to do and a good thing for
your application to avoid.
So first of all, we have this demo.
I have three simple buttons there.
There's an Add and a Remove button, and
then a Layout button.
When I click Add, it's going to add a
button to the container.
So that button should appear below.
When I Remove, it's going to remove the first button in
that container if said button exists.
And the Layout call requests layout on
the container itself.
So it basically forces a layout.
So first, I'll click Add.
And nothing happened.
But you'll see, actually something did happen.
It's just that there was no visual change to the activity.
So if I call Request Layout forcibly on the container, you
can now see, oh, well that button actually was there.
We just weren't seeing it.
Again, I can call Add again, add another button.
And in fact I have to call Request Layout to actually see
the button.
I can keep doing this over and over again.
And each time we actually add a button, but nothing happens.
Similarly, I can click on the Remove.
And a button disappears.
But the container actually should have shrunk around it.
And that simply didn't happen.
So if I call Request Layout, the right thing happens.
Again, click Remove, the button goes away.
But the right visual thing didn't happen.
So let's take a look at the code.
And then let's chat about what's actually going on.
So here's my hacked up demo.
And again, this is not necessarily the instantiation
of what I have seen in applications.
But they all boil down to essentially this logic, which
is, request layouts are getting called somewhere--
either directly or indirectly--
during the middle of the layout process.
And that is the root of the problem.
So in this particular hackey demo, we have click listeners
on the buttons for the Add View button.
We set a flag that says, hey, I'd like you to add a view.
And then we call Request Layout on that layout where
we're going to add the view.
Remove button-- similarly, we set a flag that says, hey, I'd
like to remove one of your views.
And then we call Request Layout.
And then finally, the Layout button forces a layout by
calling Request Layout on that container.
And that restores the state that we thought the container
should have been in when we did the Add and Remove
operations.
Here's a custom container.
It has those couple of flags in it.
And then it has this On Layout override.
So On Layout is a standard override from the View class.
So if you would like to do something
special in your layout--
for instance, Linear Layout overrides On Layout.
And that's where it actually lays out its children
horizontally or vertically to position them appropriately on
the screen before returning from On Layout.
So in here, we say, OK, if there's a Remove request
pending, then we're going to call this method that says,
remove button.
And then, all we're going to do is remove the first child,
if that child exists.
And if there's an Add request pending, then we're going to
add a button.
And then we're going to reset the flag.
And adding a button simply creates this button along the
way, sets the layout parameters accordingly, sets
some text on there so we know which button it is.
And then we add the view.
So the actual code to remove and add these views and set
the properties and the layout params on all the views--
all of that's correct.
The problem is that we're doing it in On Layout.
We're basically in the middle of laying out the entire view
hierarchy, or some subset of it that needed to be laid out.
And that's the time when we actually call functions to add
and remove views, which are going to
affect the layout itself.
So it's kind of a recursive problem where we're in the
middle of doing something, and we change properties that will
side effect the thing that we are trying to do.
In particular, and here's the crux of the problem, we're in
the middle of laying out the container.
And so, for instance, we call super.onLayout.
That's going to call up into the code in Linear Layout that
figures out where all the children should be.
And then, we're going to go ahead and add or remove
children after that call.
And when layout is done for any particular node in the
view hierarchy, it's going to clear some flag saying, I'm
done with layout.
So what's going to happen is, we're going to either add or
remove views in this code.
And then we're going to return back to the superclass, the
actual Layout call on the view.
And the view's going to say, OK, I'm done with layout.
And then, it continues along with its life.
Meanwhile, these guys have been added here.
And they have not had the proper side effect on the
sizing and positioning information of that view.
So they have requested Layout, which is a side effect of
calling Add or Remove view.
But that request didn't propagate, because we're
already in the middle of doing layout and figured we were
done when we finished that Layout call.
So there are many different ways to do this.
I have, in this demo, picked probably the
worst way to do it.
The easiest way for you to actually do this correctly in
this particularly hackey demo is, instead of actually
setting a flag and then forcing this thing to happen
within layout, the correct thing to do would actually be,
for example, to call myLayout.addButton
directly in that code.
Get rid of these horribly offensive lines of code.
Similarly, remove button-- you could call
myLayout.removeButton and get rid of this hackey
approach to doing it.
Now, this is rather obvious.
As I said, this is a hackey demo that shows the most
obvious example of this.
But we've seen other things that are quite similar to this
where, in the middle of someone's measurement or
layout code, someone goes ahead and does things that are
side effecting the stuff that the entire view hierarchy is
in the middle of dealing with.
You will see an approach to this in list view and adapter
view and some of the internal classes.
And I've seen some custom classes that do things
similarly externally, where the correct approach, if you
really, for some reason, need to do this in the middle of
that code, say you're a list view and
you're recycling views.
And things do get added and removed during layout.
One of the things you should keep in mind is that there are
Add View in layout and Remove View in layout.
These are the methods to call if you're actually doing these
things during the middle of layout.
But the most important thing to remember is, when you
finish layout, all of the measurement and layout
information for that container should be done.
It should be complete.
Which means, if you're adding children during the middle of
layout, you need to make sure that your measurement
information and that of your children is all correct by the
time you return from layout.
Because that's what the rest of the view hierarchy expects.
So don't do it like I did it.
Actually do it correctly instead.
And don't do this stuff during layout.
Or if you do, do it the correct way.
And make sure that when your layout for your container
finishes, everything below it is handled correctly so that
the view hierarchy is in a consistent
state with your container.
Thanks.