(tl;dr This post addresses the issues that arise when modifying the underlying collection of an ArrayAdapter and applying filtering. An alternative implementation of ArrayAdapter, tailored for easy and flexible filtering, will soon be available in the blogActivity repository)

The Android SDK is full of simple, flexible and easily extensible classes to help you accomplish many usual tasks. The philosophy is clear: most of the time, just instantiating them with a few custom parameters should suffice; and when that’s not enough, it should boil down to inheriting them and overriding a few methods.

That was most likely the intention with ArrayAdapter. It works like a charm in most scenarios: just by instantiating it and passing a few extra arguments, you can display a fully functional list backed by a collection, and even apply filters to it. When your scenario isn’t most scenarios, however, things can get annoyingly complicated.

I experienced this first hand: I tried to add some items to the underlying collection and let the user do some filtering. Things went haywire. I couldn’t get the list on screen to reflect my updates without switching adapters on the fly and rebuilding the collections myself, or manually running an empty-string filter every time.

After some (extensive) trial and error, I decided to take a look at the ArrayAdapter implementation. As it turns out, it’s actually meant to behave like that.

The problem

ArrayAdapter keeps two internal collections. At the time of creation, one (called mObjects) is pointed to the collection you provide, and the other one (called mOriginalValues) is left null.

As long as it stays null, mObjects is used both for storage and display, and everything works as expected. However, the moment you run a filter, mOriginalValues is made to hold a backup of your data and becomes the target of all future modifications, leaving mObjects to hold only the visible items.

Let’s go over that again: one collection holds the visible items, and the other one is the target of all modifications. No wonder my updates didn’t get to the screen. The only way to force the ArrayAdapter to sync is to run another filter. A bit of an ugly workaround.

The real problem

Before moving on to the solution, there’s something I’d like to point out. Even if the implementation of ArrayAdapter could be better, I don’t think that’s the real issue here.

Okay, so it can’t perform asynchronous filtering while you modify the underlying data and automagically keep everything in sync without any additional calls. That’s not so bad, is it? The real problem is that it appears to be able to do so.

Everything, from the documentation to the names of the methods and the parameters they receive, makes you think the class will do everything transparently and without further intervention from your part. It won’t, but you’ll expect it to. You’ll write code that fails, and you won’t see why, because your code does not reflect what’s really going on.

Even worse, actually: the class will behave according to your (magical) expectations at first, and then acquire a completely different behavior without much of a warning.

So, as I said, I don’t think it’s an implementation issue. I think it’s a contract issue. The class can’t do this or that? Not the problem. The problem is I was expecting differently.

Other drawbacks

There are some other, perhaps minor, issues with ArrayAdapter.

  1. It defaults and reverts to notifyOnChange(true) whenever you manually notifyDataSetChanged(). Again, this wouldn’t be so bad if you expected it.
  2. The way it deals with an initial object array is also confusing.
  3. Replacing the Filter is uncomfortable, since you can’t access any of the Adapter‘s internal state, even when subclassing.

An alternate implementation

I’m in the process of implementing an ArrayAdapter variant, tailored for easy and extensible filtering. I will include it my next post, and also upload it to the (newly created and currently empty) blogActivity repository.

I will try to address all the issues I mentioned, specially the contractual confusion. My implementation will probably be a bit inferior in terms of performance, but it will do much simpler book-keeping and have a solid, clear interface.

In short, even if it’s not as magical, it will always behave the way you expect it to.

Advertisements