Add two new posts on ListView!

This commit is contained in:
Bradlee Speice 2014-05-23 19:10:34 -04:00
parent a82a82d17e
commit 881b51b298
2 changed files with 106 additions and 0 deletions

View File

@ -0,0 +1,54 @@
---
layout: post
title: "More than you ever wanted to know about ListView caching"
modified: 2014-05-23 19:05:13 -0400
tags: [listview, viewholder, holderview, caching]
image:
feature:
credit:
creditlink:
comments:
share:
---
More than you ever wanted to know about ListView caching
========================================================
Unless you wanted to know a *lot*
---------------------------------
So, I ran into some interesting issues with `ListView` caching in trying to get everything implemented. I'm going to document them here, because I imagine I'm not the only one to struggle with these, and it could save people a lot of time and frustration later. Hope this is helpful!
Why does HolderView work anyway?
--------------------------------
One of the first questions I had about HolderView was why it actually worked. I mean, if classes are not static any more, they're being garbage collected when they go out of scope. And if they're being garbage collected, I'll have to keep on re-creating them, and I'm back to having the issues the [ViewHolder and HolderView]({% post_url 2014-05-23-viewholder-vs-holderview.md %}) were designed to solve in the first place.
Well, to understand why `HolderView` works you need to know a bit about how the `ListView` operates in the backend. Long story short, when you scroll through elements, the `ListView` recycles things from the top of the screen down to the bottom. Think of it like a [climbing wall treadmill](http://www.hammacher.com/Product/Default.aspx?sku=12219). This way, you don't have to store every single element of the list, and you don't have to create them either.
So, the `getView()` method is where the actual view inflation of a `ListView` happens. The `ListView` supplies us with one of the `View`s it's already had created for us, so we can get right to work. Now, the `ListView` will display whatever we return from `getView()`. So there's nothing against us returning a completely different view, unrelated to the one the `ListView` gave us originally. And that's why this works.
Because the `HolderView` returns it's own custom `View` object, rather than using the `View` that the `ListView` gave us, we can guarantee attributes like the `bind()` method will actually exist when cast to/from `View`/`ItemView`. This way, when the views are recycled by the `ListView`, it's actually giving us the custom `HolderView` views, rather than generic views. All we have to do is re-`bind()` the `HolderView`, and we're good to go. Fancy, no?
Switching to ViewHolder from HolderView
---------------------------------------
Now, let's say that for whatever reason, you want to switch to a `ViewHolder` pattern from a `HolderView`. Not too challenging, but make sure you're thorough about it. [When I tried to do this](https://github.com/MinimalBible/MinimalBible/commit/d664f12d0825201f64c755b2b6ecee26e2169e6b#diff-46af121991fccb227334e34062a21659L50) I wasn't so thorough, and so instead of caching only references to the `ViewHolder`, I just cached the `HolderView` instead. It led to the same bugs I was seeing prior to the switch (of course) and so I was a bit frustrated. I also started getting some NullPointerException errors, which was really strange. Understanding these errors is incredibly technical, so stick with me.
Part of the reason for using `HolderView` at all is memory management. The standard `ViewHolder` pattern uses static classes, meaning they just stay in memory until the actual application is killed. The standard `HolderView` is not a static class, which means that when it goes out of scope, it is destroyed.
Well, in the implementation linked to above, I stored the `HolderView` object into the cache in a kind of hybrid approach. I kept the memory management of `HolderView` while using the `getTag()` style of `ViewHolder` caching. And to be honest, it wasn't until trying to write this post (4 days after the fact) that I understand why it was broken. **Since the `HolderView` gets destroyed when it goes out of scope** (for example, when you scroll past the view), **the tag of the View will be null when you scroll anywhere.** Thus, I had to modify the code so that I check both the view and tag, rather than just the view:
**BookListAdapter.java**
{% highlight java %}
// Note that convertView==null will short-circuit the getTag() check
if (convertView == null || convertView.getTag() == null)
{% endhighlight %}
View recycling leaves views partially initialized
-------------------------------------------------
Now, this last piece is a very nasty issue I had seen [ever since adding](https://github.com/MinimalBible/MinimalBible/commit/b04d6c67ae0324cfaa12c3d17b2815fe08936658) the initial `ProgressWheel` (took 3 days to get it fixed). What was happening was that when I showed the `ProgressWheel`, individual rows in the `ListView` would have the wheel displayed when I had never clicked them!
Well, there wasn't ever really an a-HA! moment in trying to fix this, but by the time I had understood enough of how the `ListView` actually works to debug some of the issues above, this one started to make sense.
What happens is that the views get re-cycled when you scroll down, but the bind method never *explicitly* set the state, or inflated the layout. Thus, the view that was given already had the `ProgressWheel` showing even if the item hadn't actually clicked on it. After the fix above, everything started working again.
I hope that answers any questions you may have about the `ListView`. I think the component is challenging to use, and if you don't really understand the optimizations it does, things get very strange and confusing very quickly. But now you know enough of how everything is laid out in memory to be efficient yourself! Go forward and write great code!

View File

@ -0,0 +1,52 @@
---
layout: post
title: "ViewHolder vs. HolderView"
modified: 2014-05-23 19:04:19 -0400
tags: [viewholder, holderview, caching, listview]
image:
feature:
credit:
creditlink:
comments:
share:
---
Further semantics and practices
-------------------------------
Well, I personally think that `ListView`s are some of the most complicated `View`s in the Android ecosystem. So, I'm going to make two blog posts talking about them! The first is the ViewHolder vs. HolderView pattern, the second will be some nasty issues on ViewHolder caching I ran into. So, without further ado:
What is ViewHolder?
-------------------
One of the biggest problems in displaying a `ListView` in Android is just how inefficient it is. Specifically, calling `findViewById()` is really expensive, so we try and do everything we can to avoid it. That's why the [ViewHolder](http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder) pattern was invented. The basic principle is:
* The `ListView` gives us a `View` object when it requests a view from the adapter with `getView()`
* If that `View` is `null`, we need to inflate a new one. Then, cache it in the view with `setTag()`
* If that `View` is not `null`, we can use the View's `getTag()` method to retrieve a cached version of it (since it's been inflated prior)
So, what we actually *cache* in that sequence above is the `ViewHolder` object. Basically, it just stores a static reference to the inflated fields so we don't have to reinflate everything. Then, the actual adapter is responsible for updating the inflated `View` using the references in the `ViewHolder`.
If you have any other questions on ViewHolder, check out [this page](http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/) because the author did an amazing job of explaining everything.
What is HolderView?
-------------------
The [HolderView](http://www.jayway.com/2013/11/06/viewholder-vs-holderview/) pattern has the same basic principle. We want to make as few calls to `findViewById()` as possible. There's a big difference though: **The `HolderView` is responsible for its own presentation.**
* The `ViewHolder` just stores a reference to the `View` elements that are being inflated (by the adapter). The `HolderView` actually inflates itself.
* The Adapter driving the ListView is responsible for the presentation of data in each element. The `HolderView` handles it's own presentation through a `bind()` or some similar method.
* The `ViewHolder` is a static class, and so can't be garbage collected. Each `ViewHolder` takes up incredibly little space, but if you're OCD, you still have unused objects you can't remove from memory. The `HolderView` is allowed to go out of scope and be deconstructed.
There are a few downsides too:
* The `ViewHolder` pattern is nearly ubiquitous on the Internet. Honestly, if I wasn't forced to use `ViewHolder` by [Android Annotations](https://github.com/excilys/androidannotations/wiki/Adapters-and-lists), I would never have known it exists (I can't justify it, but I'm guessing a similar pattern is used on iOS development, feel free to let me know).
* The `HolderView` likely means you'll have another class file. You don't necessarily *have* to do this, but it's usually better that way.
* The `HolderView` is used in place of the view that the `ListView` is actually trying to inflate. That is, the `ViewHolder` just caches references to elements inside the `View`. The `HolderView` actually replaces the `View` (which means typecasting).
By any means, the `HolderView` pattern kind of acts like an [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) layer for your views. You can check out my original implementation over [here](https://github.com/MinimalBible/MinimalBible/blob/d16730781b31efc120a13a917992372956127310/MinimalBible/src/org/bspeice/minimalbible/activities/downloader/BookListAdapter.java#L51).
So the biggest benefit of using a `HolderView` pattern is that you can move the presentation logic of a `View` to its own class, and thus do some pretty cool stuff (for example, Dagger won't let you inject non-static inner classes).
So those are the two patterns! Coming up next: more than you ever wanted to know about `ListView` caching.