Every project introduces a few new challenges. Especially when you’re trying to add features that require you to do things that you didn’t to in the last project or simply haven’t encountered before. But this is the fun part of software development.
Lest we forget, software development isn’t fun unless you’re pushing yourself to do things that you didn’t do before.
If you’ve read some of the previous posts regarding updates I’ve made to the latest version of The Shopper, you’ll notice that I’ve decided to use a TabHost to hold ListView’s that act as dynamic filters for the proper item listing on the right side of the screen. As far as I know, tabs in Android are still a bit fringe. Judging by the implementation details presented in my instance, I’d have to say that it’s not the most elegant solution for tabs in a GUI environment I’ve used (frankly setting up tabs in GTK is easier IMHO). Just to clarify, I’m not talking about declaring a View in XML, rather I’m talking more specifically about populating dynamic tab content in a procedural manner.
Just in that last sentence, therein lies the challenge. Defining your layout in XML is one thing. Having to react to user input and dynamically change content is another. While you can anticipate the methods in which the user will do certain things that will require your app to change in some way, this isn’t a solution context that can be adequately described in a layout XML file. Typically, despite the obvious violation of encapsulating data from GUI, a procedural solution is sometimes better.
So what exactly am I going on about? The behavior of The Shopper requires that the content of the lists contained in the TabHost change on a frequent basis. In fact, every time the user performs a transaction that revolves around an item (think in the context of CRUD for you DB people), that list content is almost guaranteed to change. Thus for every action, in order to ensure the user is seeing the most accurate data, the lists are updated.
The process of actually updating these lists isn’t hard and any novice Android developer can do this. Using Fragments as I have here only adds a small layer of complexity in the form of interface-based delegation through the hosting Activity. Again nothing too seriously mind bogging.
The problem comes in when you actually try to get access to a tab’s content. It’s not as straightforward as it seems. I also may be overcomplicating things but bear with me.
TabHosts contain TabWidgets. TabWidgets are the Views that hold the actual tabs. Each TabWidget hosts an arbitrary number of tabs that can be specified in either your layout XML or via code. Procedural exploration shows that TabWidgets actually contain what are called TabSpec instances. TabSpec defines an individual tab that will be inserted in the parent TabWidget via the TabHost (the API call to perform this operation belongs to the TabHost).
My first logical instinct was to assume that a TabWidget had an API that went something like this:
It’s extremely raw and definitely shallow (also totally unrealistic) but it makes logical sense and looks nice syntactically. One would think that because a tab (TabSpec more specifically) contains content that you could reference the View and use the findViewById API to update the content.
Turns out that it doesn’t really work like that. The API for TabSpec is extremely sparse. All it allows you to do is set the content and set the indicator (inflated View content and the text/View that shows on the tab). There really isn’t anything that you can do insofar as updating the content is concerned.
I decided then to go one layer up, to the TabWidget, to see if it would provide me with anything useful. Unfortunately it doesn’t; the design here really reflects well on the Android team for making sure that individual components stay separate entities which you have to admire them for. Upon first glance, some of the methods seem like they would work for the purpose: getChildTabViewAt for example. However this, and methods like it, only select the tabs themselves, not their content.
So here I was back at the TabHost after having gone down as far as I could go and working my way back up gradually with no success. Back at the TabHost, I knew a few things:
- Tabs can be added or removed via the TabHost in a procedural manner. Adding tabs can be performed individually but removing tabs can only be in a Draconian manner (i.e. remove either all of them or none of them).
- Tab content can be generated in a procedural manner via TabSpec. A new TabSpec can be added to a TabHost thus making it a visible tab to the user.
While completely barbaric in some sense, my solution thus became outlined in pseudo-code as such:
- Obtain a reference to the known existing TabHost
- Instantiate required variables of respective types for operation
- Use the TabHost to remove all tabs and their content
- Repopulate the data from the database into transitive objects
- Configure new TabSpec instances by assigning a text indicator and inflating a View that contains the ListView for the respective tabs. The ListView adapters would be assigned here using the transitive objects as data basis.
- Apply custom styling to the TabWidget
In other words, what this all meant is that due to API restrictions, I am essentially removing all existing tabs from the TabHost, manually rebuilding and assigning them back to the TabHost. Like I mentioned before, this is quite barbaric and costly in some cases but for the time being, this seems to be the more mind-settling solution for me.
What made me do this? Nothing else I found on the matter seemed to satisfy me and remain simple. Occasionally I stumbled across a few people who suggested the use of ViewSwitcher and the equivalent for Activities (the name of which escapes me now). Logically these didn’t make any sense. All I wanted to do was update content for an existing ListView and not swap between an arbitrary number of them (which is what a ViewSwitcher is designed to do and is far more suitable for View animations than anything). More importantly, the ListView was self-contained meaning that changes or interactions in one tab didn’t directly or indirectly affect the content of another tab. This same logic applies to the ActivitySwitcher only backed with the argument of additional complexity of having multiple Activities instead of multiple Views.
The other argument made is why not just check to see if the tab content’s parent layout View exists and use findViewById? The content of the tabs is generated in a procedural manner to begin with. Thus at the time of initial layout inflation, they don’t technically exist. This conditional, however, is used to determine the current orientation of the app. More specifically, the existence of the View that contains the TabHost rather than a parent View for TabSpec content.
So until I find a more efficient way of doing things, this is going to be my toss into the tab content updating solution.