Filterable ArrayAdapter Followup

November 9, 2010, 6:01 pm

A couple of points to follow up my previous ListView post:

Top left hand corner bug with the workaround

There is actually a hack that will force the ListView to re-request the section names, but unfortunately it causes the section name overlay view to re-render at the top left corner of the screen:

@Override protected void publishResults(CharSequence prefix, FilterResults results) { // ... Turning fast scrolling off before updating our // index and then turning it on again will cause // getSections to be called, but it messes up the fast scrolling // section name overlay lv.setFastScrollEnabled(false); updateListIndex (); lv.setFastScrollEnabled(true); // ... }

How AlphabetIndexer does it

When I was writing this post I discovered the AlphabetIndexer class, a helper class that implements the sectionIndex interface:

If the items in the adapter are sorted by simple alphabet-based sorting, then this class provides a way to do fast indexing of large lists using binary search. It caches the indices that have been determined through the binary search and also invalidates the cache if changes occur in the cursor.

Having a look at the source for this guy, we can see that it uses a similar fixed set of section names that are intialized based on the alphabet passed into the constructor. So if you are filtering a large list with a diverse range of section names, you will have the same problems that exist with my SectionIndexer hack.

Permalink - Comments - Tags: Development,Android,Google

A Filterable ArrayAdapter with Fast Scrolling in Android

November 6, 2010, 12:30 pm

I spent today at the Google Sydney offices for an Android Developer Lab. It is my experience with events like this that you pick up handful of useful bits of information from the presentations, but the real value is the people you talk to during the day and the stuff that you build during the coding labs. My time in the lab was spent chasing down an issue with ListViews, Filterable ArrayAdapters and setFastScrollEnabled.

The issue raises its head when you implement a filterable and section indexed ArrayAdapter for your ListView and then call setFastScrollEnabled to enable the thumb button fast scrolly thing on the right hand side of the list:

Enables fast scrolling by letting the user quickly scroll through lists by dragging the fast scroll thumb. The adapter attached to the list may want to implement SectionIndexer if it wishes to display alphabet preview and jump between sections of the list.

Filterable ArrayAdapter

The first part, building a filterable android.widget.ArrayAdapter, is pretty straightforward. You implement the android.widget.Filter abstract class and store it as a member of your ArrayAdapter subclass. ArrayAdapter has a getFilter method which returns your custom Filter implementation to the ListView.

The Filter interface has a couple of methods that let you create a subset of your ListView data based on the search term that user types on the keyboard. Called in a worker thread performFiltering does the actual work adjusting the ListView content based on the CharSequence parameter. The publishResults method publishes the results of the filtering operation to the UI thread. The publishResults method will look something like this:

@Override protected void publishResults(CharSequence prefix, FilterResults results) { //noinspection unchecked mItems = (ArrayList<GlossaryEntry>) results.values; updateListIndex (); // Let the adapter h about the updated list if (results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } }

If we have some data after the filtering operation, then tell the ListView that its data set has changed via the notifyDataSetChanged method, otherwise just tell it to invalidate its contents (so it can render a blank view).

This method is significant for what I am going to talk about next, because it is the mechanism with which I can tell the ListView that it's stuff has changed and it needs to re-draw. I'll come to that in a minute.

SectionIndexer implementation on ArrayAdapter

SectionIndexer is the interface on the ArrayAdapter that allows the ListView to find out how the items should be broken up by section and what text to display as the fast scroll UI is dragged on the right hand side of the view:

Interface that should be implemented on Adapters to enable fast scrolling in an AbsListView between sections of the list. A section is a group of list items to jump to that have something in common.

So this interface has three methods:

  • getPositionForSection(int section) - Provides the starting index in the list for a given section.
  • getSectionForPosition(int position) - This is a reverse mapping to fetch the section index for a given position in the list.
  • Object[] getSections() - This provides the list view with an array of section objects.

So when I initilise my ListView, you need to build an index to map from items to sections, sections to items and the list of section names. In my first implimentation of this stuff I did this organically based on the actual items in the list. I assumed that when I called notifyDataSetChanged the ListView would call getSections on the SectionIndexer so it can get the section names that correspond to the new content. Alas, this is not the case.

public ListIndex (int size, Delegate d) { // Build an index map m_sectionNames = new ArrayList<Object>(); m_sectionForPositionArray = new ArrayList<Integer>(); m_positionForSectionArray = new ArrayList<Integer>(); String prevSection = ""; m_Size = size; for(int i = 0; i < size; i++) { //String sectionName = names[i].substring(0, 1); String sectionName = d.sectionNameForIndex(i); if(!prevSection.equals(sectionName)) { m_sectionNames.add(sectionName); m_positionForSectionArray.add(i); prevSection = sectionName; } m_sectionForPositionArray.add(m_sectionNames.size() - 1); } }

So this implementation will build an m_sectionNames array that will be different for each subset of the ListView content. Because getSections isn't called when you publish a filtering operation to the listView, you will get index out of range errors. The solution I came up with during the lab was to build a section names array based on a fixed alphabet, rather than the variable contents of the ListView:

public ListIndex (int size, Delegate d) { // Build an index map m_sectionNames = new ArrayList<Object>(26); m_sectionForPositionArray = new ArrayList<Integer>(); m_positionForSectionArray = new ArrayList<Integer>(26) ; for (int j = 0; j < 26; j++) { // populate all the position for sections with -1 m_positionForSectionArray.add (-1); // add the section names for the alphabet m_sectionNames.add (String.format ("%c", j + 65)); } // now run through the index int currentIndex = 0; for(int i = 0; i < size; i++) { String sectionName = d.sectionNameForIndex(i); char c = sectionName.charAt(0); int sectionIndex = currentIndex; if ((int)c >= 65 && (int)c < (66 + 26)) { // if this term has an alpha first character, store the section Index sectionIndex = (int)c - 65; currentIndex = sectionIndex; } // set the position for section array with the first value we find if (m_positionForSectionArray.get(sectionIndex) == -1) m_positionForSectionArray.set(sectionIndex, i); // add the section for position item with the last valid sectionIndex we got m_sectionForPositionArray.add(sectionIndex); } // now we have to run through our position for section and make // sure we don't have any gaps left currentIndex = -1; for (int i = 0; i < m_positionForSectionArray.size (); i++) { if (currentIndex == -1 && m_positionForSectionArray.get(i) != -1) { // have our first index ... go back through the array // and set the values currentIndex = m_positionForSectionArray.get(i); for (int j = 0; j < i; j++) m_positionForSectionArray.set(j, currentIndex); continue; } if (m_positionForSectionArray.get(i) != -1) { currentIndex = m_positionForSectionArray.get(i); continue; } if (currentIndex != -1 && m_positionForSectionArray.get(i) == -1) { // fill any gap values with the last index m_positionForSectionArray.set(i, currentIndex); continue; } } }

So this implementation is less than ideal for a couple of reasons. It will miss-categorize items that don't start with an alpha character and it will include sections with no entries (the index is forced to the next available item). It would be much nicer if the ListView just re-grabbed the section names when you call notifyDataSetChanged.

Permalink - Comments - Tags: Development,Android,Google

Jared Grimes Tap

October 28, 2010, 6:30 pm

Jared Grimes, amazing tap teacher in New York, made an instructional DVD a few years back. My tap teacher at the Sydney Dance Company, Tracey Wilson, taught Jared's "Route 66" number and Carolyn Yates filmed and edited it. Here is the result:

Update 26th July 2011

If you are looking for more Tracey Wilson tapping, she and I have collaborated to create Tapsteps, an iPhone, iPad & iPod app to help people learn to tap.

Permalink - Comments - Tags: Misc

One code base, two applications on Android

October 1, 2010, 10:19 am

It was my understanding that application package name and the internal java package names in your Android project were distinct. Application package names need to be globally unique, internal package names can be whatever the hell you like. So, if you are using the same code base (with various resources) to build multiple applications, this all should be fairly painless ... you would think.

Turns out, this is a massive pain in in the arse. If you change the package attribute for the manifest element in your AppilcationManifest.xml (this is the application package name, the one that needs to be unique) to something other than the package containing your Application object, you will quickly enter a world of hurt. In my case I am trying to use net.cannonade.glossary for my internal java package name and net.cannonade.ballet for my application package name.

As this post notes, once you change the application package name you will discover that all your source files will compain because they can no longer locate the resources that they were previosly getting from net.cannonade.glossary.R.

At this point I probably should have stopped. If I am writing script to run through all my source and modify code from net.cannonade.glossary to net.cannonade.ballet, then my ideal of having generic source and application specific resources seem unattainable. But I had commited to trying to figure this out, so I stumbled blindly onward.

So you go through all your source files and add:

import net.cannonade.ballet.R;

This resolves all the compile errors because my application classes can now find the generated resource files. Seems painfull, but all working so far. In fact, I run the app at this point and it all seems to work fine.

Everything was not as it seemed. It seems Eclipse gets very confused about which app it is debugging if there are any previously installed apps on the emulator (with different appilication package names). So it seemed like it was working fine, because it was running my previous build with the old package name. So you need to start fresh and delete those old binaries from the emulator. To do this you need to dig through a alarming number of settings screens:

Application Screen/Settings/Applications/Manage Applications/app/uninstall/OK/OK

Snarky side note - To do this on the iPhone, I would have to:

long press app/X/OK

Ok. So once I figured this out and was debugging the right binary, I discovered a problem that I have not been able to figure out.

When I need to show a new activity in my app, I use the following piece of code:

Intent newIntent = new Intent(); newIntent.setClassName("net.cannonade.glossary", "net.cannonade.glossary.GlossaryRoot"); startActivity(newIntent);

The first time I do this is from my android.intent.category.LAUNCHER activity, SplashScreen.java. My splash screen code just shows my company logo for a short timeout and then starts my root activity (using the code above).

Another snarky side note - To do this in XCode, create default.png

My problem is that startActivity throws an ActivityNotFoundException:

Unable to find explicit activity class {net.cannonade.glossary/net.cannonade.glossary.GlossaryRoot}

My intial thought was that, as a result of the changed application package name, the package name was different in some way. I thought the best way to determine what the new package name/class name should be is to update my application manifest to use GlossaryRoot as my android.intent.category.LAUNCHER activity and then check what those two strings should be by querying them from the loaded activity in the onCreate method:

String thisPackage = this.getClass().getPackage().getName(); String thisClass = this.getClass().getName();

So my app loads up with GlossaryRoot as the new LAUNCHER activity and I expected the thisPackage and thisClass variables to contain something other than net.cannonade.glossary and net.cannonade.glossary.GlossaryRoot respectively.

To my dismay I discovered that when queried from the launched activity, the package name and class name matched what I was passing into my intent at run time. So basically if the Activity is loaded based on the manifest LAUNCHER attribute, it is fine. If I try and load it at run time, with exactly the same package name and class name, it fails to find the activity.

I am stuck and have decided, given that I have to modify all my source anyway, I will just update my script to use net.cannonade.ballet everywhere. When I need to ship a new app with different resources, I will update my script to change it to that new package name.

So perhaps I am doing something fundamentally wrong here. Perhaps there is simple way of getting around this problem. If there is an Android gronk out there with some answers, I would love to hear from you.

Permalink - Comments - Tags: Development,Android

Tingo Teams on This Week in Startups

September 27, 2010, 9:40 pm

I worked up some courage and went to the local This Week in Startups meetup hosted by Matt Stone of simXchange and organized by Leo Perez of Pure Nutrition. It was very interesting meeting up with other Sydney startups and talking about their projects.

I was very excited (and terrified) to get a chance to pitch to Jason Calacanis, Tyler Crowley and Lon Harris and I was blown away by the positive reaction to our product and the awesome feedback that we got.

To top of the experience, when I got back to my desk I watched the end of the show to see Tingo Teams chosen as the best pitch. This was 100% down to the awesome product and very little to do with my abilities as a salesman, which is definitely better than the other way around.

I can probably stop talking about this now.

Permalink - Comments - Tags: Development,iPhone