Tingo Family on This Week in Mobile

December 13, 2010, 4:30 pm

I did an interview with Ashley Esqueda and Mike Hobbs on This Week in Mobile. Here is the clip:

Permalink - Comments - Tags: Development,iPhone

Phonics Deck Artwork

December 10, 2010, 12:16 am

I have spent a lot of time over the last few months working on my next iOS app. From a development perspective, it went pretty quickly. I built a universal app (iPhone & iPad) with arbitrary orientation from the start and that was pretty satisfying. I had failed to do that with Monster Memory and I think the final product suffered as a result.

So the app itself was built in a few weeks. The bulk of the time was doing the artwork for the 42 cards that are managed by the application. In hindsight, it was an insane project to embark upon and I am very glad to be finished. Here is the full set of cards in the app:

Ok ... I was scraping the bottom of the creative barrel when I got to truck.

Permalink - Comments - Tags: Development,iPad,iPhone,Art?

How Life360 gets it wrong

November 25, 2010, 10:52 am

Full Disclosure I am a developer on Tingo Family a location based family safety app that is a direct competitor to Life360. These opinions are my own and do not necessarily reflect those of my employer.

Life360 has a fundamental premise that parents want (and need) to know where sex offenders live in their area. When I log into the Life360 web interface I get the following rather alarming message:

I have issues with this premise:

You know the Offenders in your neighbourhood...

Really?! First of all the data that is being used to populate their maps is extremely suspect. Clicking on the map button underneath the dangerous looking mug shot, I get the following screen:

So this guy, who may well be a sex offender registered in Holmes Drive Colorado, doesn't live in General Holmes Drive in Sydney. Ok, this is a US service and I can forgive techincal issues that Life360 might not have seen for non US locations, but it certainly doesn't fill me with confidence. My real issue is why this data, assuming it is accurate in the US, is relevant in the first place? It is based on the assumpation that if you check this map and your child is arbitarily distant from a certain set of known sex offenders they are ok and if not, sound the alarm!

Protect Your Kids From These Offenders

This is a false assumption. There is data that indicates 60% of boys and 80% of girls, who are assaulted, are sexually abused by someone known to the child or the child's family (Lieb, Quinsey, and Berliner, 1998). It is also sadly true that only a tiny fraction of sexual offenders are apprehended and convicted for their crimes. So the conclusion, that knowledge of the address of registered sex offenders is useful, is misguided at best and potentially dangerous. I believe this kind of functionality is capitalizing on people's fear and rather than helping families protect their children, by creating a false sense of security, puts them at risk.

Perhaps an analogy will help illustrate my point. Imagine an iPhone application that indicates where 5% (and I am being generous here) of cars are parked. These locations are mapped alongside the current location of your children. Does this application help you protect your kids from being hit by a motor vehicle? I don't think so.

If someone from Life360 would like to comment, I would love to hear your point of view.

Permalink - Comments - Tags: Development,iPhone

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