Tabular Spectacular

Yesterdays article about data sources and tables did not come out of nowhere. I’ve had to deal with various issues with tables and filling them from network sources, on the one hand. On the other hand, friends of mine are working on a cross platform UI toolkit that hopefully solves many of my complaints; I like to give them my view on ideal APIs whenever I can.

So that previous post of mine created a few more questions, which I’d like to answer today. As a result, this article is going to be in relatively unrelated pieces…

Three20

One thing I was made aware of yet again is that the Three20 library attempts to solve some of the issues I outlined yesterday, especially with regards to filling table from data received over the network.

Here’s the problem: Three20 is a framework, and as such suffers from a bad case of frameworkitis; that is, either do stuff the way the Three20 author envisioned, or be fucked royally up the arse.

I prefer toolkits. The API I outlined is not perfect in that sense, but it gives most control to the data source implementor, that is you. If you were to use it, that is.

More Control

One thing that happens on both Android and iOS is that instead of returning data to from the data source to the view (or, in the iOS case, the delegate), views are returned. That is, to use my previously envisioned API as a starting point, there would be no getData() call.

Fine. Make it a getViews() call and give more control to the user. I’d love that. You can even merge it with the call to get a view for the pending, empty or user-defined states.

Even More Control

If you’re doing that, you might wonder why we return child views to the list view in the first place. We could just let the data source manipulate the list view’s children directly, right?

Let’s explore that for a bit.

The main reason I didn’t propose that is because the list view contains some state that we don’t necessarily want the data source implementor to reimplement the same thing over and over again. What the list view encapsulates right now, more or less, are the following things:

  1. It knows how many items are available. Well, technically, that’s not true. It really only knows how many items the data source has returned so far. But that, plus knowledge of the type for each of these items, is good enough for it to decide how to configure the scrollbar(s) it displays.
  2. It caches and re-uses views.
  3. It grabs events from the UI system, and decides whether and how the view is scrolled.
  4. It therefore knows its scroll position.
  5. It can decide not to request any data, if it’s e.g. completely off screen, invisible, or fully transparent.

All in all, there are tons of things it does that I wouldn’t want to burden the app developer with. But if you want a UI toolkit that behaves fully like a toolkit, you could write one, of course, and expose all of the above as a set of functions rather than as a class.

I have the feeling that my proposed API strikes a good balance between granting control to the user, and avoiding boilerplate code. YMMV.

Caching

One of the things I’ve written about at length in the previous article is caching of results. In particular, I’ve mentioned caching of cell data. If you write a data source that requires fetching data from the web, you’ll need to also use some form of cache.

It’s entirely possible to write a simple cache that you can choose to use in your data source implementation, or ignore if you don’t need it. The basic premise to build it on would be to consider cell data as opaque, serialized data.

  1. Offer to construct the cache with a persistent backend or in memory.
  2. Offer an API call for retrieving cache date:
    // Data is a map for each index in [offset, offset+size)
    // to a data element or null
    data = cache.getData(offset, size);
  3. Storing data in the cache requires a map again, so you can store non-contiguous cells easily.
    // The timeout parameter is optional
    cache.writeData(data_map, timeout);

And that’s pretty much it. There’ll be some details in the cache implementation that require a bit of thought, but the API can be as simple as this.