August 16, 2010 0

Today’s Meetup: GTUG

General

Excellent meetup today over at Startpad in downtown Seattle. We got to listen to a few folks show off some GWT projects. Overall it was interesting. I can’t say that the direction we’re headed with mobile and web development will see us using any GWT soon, but as things start rolling, this is something we’ll definitely start looking at as we foray into complex, interactive AJAX-y web UI/UX.

The best part was networking with other developers. The lead of Goto, a home screen replacement for Android, shared with us some challenges he and his team have overcome and how his team operates. I look forward to see what else he and his partners at Innoweb Tech release next.

These meetups are a great place to network. It takes a little effort to step out of our shell but well worth it. Sometimes I do sit there and wonder if some of those that make it are just there for the free pizza. Either way, see you there next month!


August 16, 2010 0

ListActivity and Missing Android.R.id.list

Development

It’s three in the morning, you are banging at setting up a sweet list. Things are pretty and there is a nice flow with a loading screen within your Activity. You have your post loading layout with the proper code:


You run your application and get hit with an exception which reads something like this:

java.lang.RuntimeException: Your content must have a ListView whose id
attribute is 'android.R.id.list' 

“What the heck?!”, you think, “@id/android:list is right there!” You browse Google and wade through examples. The documentation makes vague references to something regarding android:empty which is not what you are looking for (well it code be something else you are looking for).

The conclusion is to add a little ListView with that id to make sure it satisfies the requirement that the ListActivity has a ListView of that id in it before you load your final ListView which you will be populating with data:


And now you should have conquered that dreaded exception. What it comes down to is that an Activity extending from ListActivity must have that special id defined within the layout setContentView() or don’t set the view until you are ready. However, most users probably want to see something rather than staring at a black screen waiting for your ListView Activity to load. :)


July 14, 2010 0

ArrayAdapter Filtering and You

Development

Having a list of things you can view on your phone is a no brainer. Contacts, images, vacation spots; the Android ArrayAdapter widget gives us an easy way to do this. Now the examples given inside the docs is a nice example if we’re just looking at a list of strings but stops way short of being useful to those of us feeding an ArrayList to the adapter.

After looking around you’ll start finding a variety of ways to accomplish filtering. Here is how we accomplished this task filtering an ArrayList of objects. The key was to look at the ArrayAdapter source and make sure some key functions are overriden. It was frustrating having to search through poor examples, vague references, and lame answers on forums.

Firstly, you’ll need to make sure your custom adapter has this feature enabled.

getListView().setTextFilterEnabled(true);

Next when you set up your onItemClickListener make sure you are pointing to the ArrayList that was passed to the adapter and set inside. There maybe a better way of accomplishing this instead of making mItems public but for now this’ll work.

    private OnItemClickListener contactListItemClickListener = new OnItemClickListener() {
        public void onItemClick(AdapterView parent, View view, int position, long id) {
        	ItemObject item = mItemObjectAdapter.mItems.get(position);
            Intent intent = new Intent(ActivityItems.this, ActivityDetails.class);
            intent.putExtra(EXTRA_STUFF, item.getId());
            startActivity(intent);
		}
    };

Start your custom adapter by implementing the Filterable interface. You will need to override all the functions specifically dealing with grabbing things from your ArrayList. The getView is the given one.

private class ContactAdapter extends ArrayAdapter implements Filterable {
    	private final Object mLock = new Object();
    	private ItemsFilter mFilter;
        public ArrayList mItems;
        /*  ...   */
        @Override
        public int getCount() {
        	return mItems.size();
        }
        @Override
        public Item getItem(int position) {
            return mItems.get(position);
        }
        @Override
        public int getPosition(Item item) {
            return mItems.indexOf(item);
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        /*  ...   */

And now for the Filtering. So implementing this interface is a easy. It is one function. Notice how we declare mFilter above.

        /**
         * Implementing the Filterable interface.
         */
        public Filter getFilter() {
            if (mFilter == null) {
                mFilter = new ItemsFilter();
            }
            return mFilter;
}

Now the actual filter that is returned is all you. There are some basics here. We extend Filter which if you check out the code requires to only worry about two abstract functions. Here they are:

        /**
         * Custom Filter implementation for the items adapter.
         *
         */
        private class ItemsFilter extends Filter {
            protected FilterResults performFiltering(CharSequence prefix) {
            	// Initiate our results object
            	FilterResults results = new FilterResults();
            	// If the adapter array is empty, check the actual items array and use it
                if (mItems == null) {
                    synchronized (mLock) { // Notice the declaration above
                    	mItems = new ArrayList(mItemsArray);
                    }
                }
                // No prefix is sent to filter by so we're going to send back the original array
            	if (prefix == null || prefix.length() == 0) {
            		synchronized (mLock) {
            			results.values = mItemsArray;
                        results.count = mItemsArray.size();
            		}
            	} else {
                        // Compare lower case strings
            		String prefixString = prefix.toString().toLowerCase();
            		// Local to here so we're not changing actual array
                    final ArrayList items = mItems;
                    final int count = items.size();
                    final ArrayList newItems = new ArrayList(count);
                    for (int i = 0; i < count; i++) {
                        final Item item = items.get(i);
                        final String itemName = item.getItemname().toString().toLowerCase();
                        // First match against the whole, non-splitted value
                        if (itemName.startsWith(prefixString)) {
                            newItems.add(item);
                        } else {} /* This is option and taken from the source of ArrayAdapter
                            final String[] words = itemName.split(" ");
                            final int wordCount = words.length;
                            for (int k = 0; k < wordCount; k++) {
                                if (words[k].startsWith(prefixString)) {
                                    newItems.add(item);
                                    break;
                                }
                            }
                        } */
                    }
                    // Set and return
                    results.values = newItems;
                    results.count = newItems.size();
                }
            	return results;
            }
            @SuppressWarnings("unchecked")
            protected void publishResults(CharSequence prefix, FilterResults results) {
                //noinspection unchecked
            	mItems = (ArrayList) results.values;
            	// Let the adapter know about the updated list
                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
}

As long as the ArrayAdapter is properly overridden then things should work as expected. The keys are to enable the behavior, override, implement the interface, and then make available the class that will be used for filtering. The filtering logic itself is fairly straight forward. Since we expect a filter to be requested, if no string (or as we called it prefex) is passed then return the full unadulterated list of items. Otherwise, grab whatever detail you are filtering against and compare its start to your input prefix string.

Hope that helps someone.


April 9, 2010 1

Create a Button with an Image and Text [Android]

Development

The Android SDK docs aren’t very helpful. Period. More specifically, they aren’t clear on how to create a button with both an image and text. Allow me to help.

Place the following in your layout XML file, replacing [image] with your image:

Using android:drawableTop="@drawable/...", we’re able to place an image above the button text. The following options are available for placing the image in different positions relative to the button text:

android:drawableLeft
android:drawableRight
android:drawableBottom
android:drawableTop

In addition, android:drawablePadding can be used to specify the amount of padding between the image and button text.

Here’s the Android SDK documentation for specifics.