ClickAider
You are currently browsing the General category.
RSS feed

Section Headers for Android ListViews

When building the Android version of the iLike Concert app, I wanted to have day headers for the concert list.

This was easy on the iPhone; sections are built into the oUITableView class.

Surprisingly, the ListView class in Android doesn't provide any corresponding functionality, as of Android 2.1.

Jeff Sharkey's SeparatedListAdapter provides one way to get this functionality; it glues together a number of individual adapters for each section into an overall list.

In my case, I had one SimpleCursorAdapter for the whole list, so I didn't want to break it into individual adapters just to display sections.

Instead, I made the header a part of the list cell layout, and hid the header on the rows where I didn't want to display it. The headers look as follows:

My layout was like as follows; note the use of the explicit background color and textColorHighlight in the date header to prevent it from appearing as selected.

XML:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3.     xmlns:android="http://schemas.android.com/apk/res/android"
  4.     android:layout_width="fill_parent"
  5.     android:layout_height="wrap_content" 
  6.     android:orientation="vertical">
  7.  
  8.   <TextView android:id="@+id/date_header"
  9.             android:textSize="20px"
  10.             android:textStyle="bold"
  11.             android:textColor="#000"
  12.             android:textColorHighlight="#000"
  13.             android:background="#DDD"
  14.             android:visibility="gone"
  15.             android:layout_width="fill_parent"
  16.             android:layout_height="wrap_content"/>
  17.  
  18.   <LinearLayout
  19.       xmlns:android="http://schemas.android.com/apk/res/android"
  20.       android:layout_width="fill_parent"
  21.       android:layout_height="77px"
  22.       android:gravity="center_vertical"
  23.       android:orientation="horizontal">
  24.  
  25.     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="100px" android:layout_height="75px" android:gravity="center_vertical|center_horizontal" android:orientation="horizontal">
  26.       <ImageView android:id="@+id/image" android:layout_width="75px" android:layout_height="75px"/>
  27.     </LinearLayout>
  28.    
  29.     <LinearLayout
  30.         xmlns:android="http://schemas.android.com/apk/res/android"
  31.         android:layout_width="fill_parent"
  32.         android:layout_height="wrap_content"
  33.         android:gravity="center_vertical"
  34.         android:orientation="vertical">
  35.      
  36.       <TextView android:id="@+id/title"
  37.                 android:textSize="20px"
  38.                 android:textStyle="bold"
  39.                 android:layout_width="fill_parent"
  40.                 android:layout_height="wrap_content"/>
  41.       <TextView android:id="@+id/description"
  42.                 android:textSize="14px"
  43.                 android:layout_width="fill_parent"
  44.                 android:layout_height="wrap_content"/>
  45.     </LinearLayout>
  46.   </LinearLayout>
  47. </LinearLayout>

The view binder assumes a function called isHeaderVisible to decide whether the header should be visible for the current row in the cursor; implement as appropriate for your application. (For example, our application displays a header whenever the date of the current row is different from the previous row.)

JAVA:
  1. SimpleCursorAdapter listAdapter = new SimpleCursorAdapter(mContext,
  2.                 R.layout.list_item,
  3.                 mEventsCursor,
  4.                 new String[] {"artist_75x75_image_url", "artist_name", "venue_name", "event_date_string" },
  5.                 new int[] {R.id.image, R.id.title, R.id.description, R.id.date_header });
  6.     final int nDateIndex = mEventsCursor.getColumnIndex("event_date_string");      
  7.  
  8.     listAdapter.setViewBinder( new SimpleCursorAdapter.ViewBinder()
  9.         {
  10.             public boolean setViewValue(View view, Cursor cursor, int
  11. columnIndex)
  12.             {
  13.                   if (columnIndex == nDateIndex) {
  14.                     if (isHeaderVisible(cursor)) {
  15.                         view.setVisibility(View.VISIBLE);
  16.                         prevDate = dateString;
  17.                         ((TextView) view).setText(dateString);
  18.                     } else {
  19.                         ((TextView) view).setText("");
  20.                         view.setVisibility(View.GONE);
  21.                     }
  22.                     return true;
  23.                 }
  24.                 return false;
  25.             }
  26.         });

Beyond 411 for the iPhone is now available in the App store

The local search app Beyond411 (aka Berry411) is now available as a free download for the iPhone in the app store, just search for the abbreviation "b411". This is just an initial version and doesn't have all of the features of the Blackberry version. Be forgiving of it's faults, and please pass on your bug reports and suggestions for improvement.

Stock Prediction, Web Sentiment, and Search Engines: a privacy thought experiment

If Microsoft or Google found a way to predict and profit in the stock market by mining search logs, would it be a violation of their privacy policy?

This is not an outlandish scenario. The research paper Stock Prediction using Web Sentiment describes "a novel way to do stock prediction using web sentiment". The authors do textual analysis of financial message boards to extract how people are feeling about different stocks, correlate past sentiments with actual stock performance, and predict future stock values based on current sentiments.

The authors claim a strong correlation between web sentiment and future stock prices. Suppose for the sake of argument that you could actually predict and profit in the stock market with this strategy.

In principle, it seems that your ability to predict the stock market might be even better if you had access to the web search logs-- users would presumably reveal things in their private searches they wouldn't state publically. (To venture into even murkier privacy waters, consider also mining private emails.)

Google and Microsoft, of course, do have access to this data via their various web properties, and are growing increasingly sophisticated in their ability to mine this data.

They already analyze and expose aggregate search trends, for example at this moment Google Trends. At this instant Google Trends tells us that Copenhagen, Christmas Music, and Tiger Woods updates are on people's minds.

Suppose a talented Google engineer decided to create "Google Stock Trends" as a 20% project. I can't find anything in the Google privacy policy that would prevent that kind of aggregation-- it doesn't reveal any personally identifiable information.

Stock trend analysis seems to be included in the allowed purposes that Google makes use of personal information, including the vague "development of new services".

At the same time, it seems a violation of trust to think that others might profit from my revealing personal stock knowledge via a search engine query. I might not even know they were doing this mining, if Google Stock Trends was for internal use only.

Presumably a reputable company would actually never create such a tool. Should their privacy policies be more explicit that this kind of value extraction and data mining isn't allowed from searches that we think are private?

Stock Prediction

Skinny controllers, fat models and Cocoa

Skinny controller, fat model is a good design principle not just for Rails, but for client apps in MVC frameworks like Cocoa.

I think it's not uncommon, though, for client apps to have fairly anemic models, to the detriment of reusability. For example, I recently worked with an open source module that was in general very well written, but which made it challenging to reuse to underlying parsing logic.

Why this tendency for skinny models? In part, this might because client apps are UI-centric and this ends up being reflected in the design of the code. Even Apple's naming conventions (e.g. DetailViewController) emphasize the controller and view at the expense of the model.

The lack of a full-fledged read-eval-print loop in Objective C (akin to the Rails console) also makes it inconvenient for developers to exercise fat models. MacRuby has potential to provide an interactive console for Objective C models, and could perhaps be integrated with Xcode to allow me to bring up a console on any app.

Browsing iPhoto from a Blu-Ray player using iPhotoFS

Below are some (terribly blurry) pictures of me browsing my iPhoto albums on a network Blu-Ray player.

I just installed and ran the latest version of iPhotoFS, clicked the "Shared folder" checkbox on the Get Info box for the iPhotoFS device, and browsed to the shared folder on the Blu-Ray player. There are top level folders to browse by album, roll, or date.

iPhotoFS alpha on Google Code

iPhotoFS is a read-only filesystem for iPhoto collections, built on top of MacFUSE. I wrote iPhotoFS to let me easily copy my iPhoto Library to my PC while retaining its structure. There are subfolders for photos organized by roll, album, or month.

It allows you to easily interfact with your iPhoto collection using the Finder and command line utilities like `find` and `rsync`. You can even browse collections on other machines by viewing the share volume.

Download iPhotoFS on Google Code, it's open source under an Apache license.

Berry 411 for the iPhone submitted to the app store

I've submitted Berry 411 for the iPhone to the App Store and am hoping for quick approval. It will be free.

(See a screencast here.)

I have many ideas for future improvements but I'm pretty happy with how it turned out. Being able to get driving directions, reviews, and web pages for a business without leaving the app is handy indeed.

I'm looking for a small number of beta testers to try it out while i wait for approval. Please email your device ID using the Ad-Hoc Helper if interested. (Apologies if I don't have time to send beta copies to everyone who replies, but hopefully it will be in the App Store soon in any event.)

iPhone 411 screencast

I'm just about done recreating Beyond411 on the iPhone. I created this brief screencast to get your feedback on the app before I submit it the iTunes stores.

The key features are as follows:

* Find nearby local business information by name or category
- 20,000 word autocomplete dictionary
- 500 item directory of popular businesses
- Fast list interface

* Everything you need to know without leaving the app
- Driving directions
- Reviews, Images, Web Links with inline browser
- Call Business
- Show directions in Google maps

Please let me know what you think and what I can improved!


xml_grep cookbook

iLike frequently receives large XML files from our partners that we need to analyze.

For ad-hoc analysis of XML from command line, xml_grep is a really valuable tool. Instead of extracting lines that match regular expressions, xml_grep extracts xml nodes that match xpath expressions.

However, it took me a bit of trial and error to figure out how install and use it successfully. Here's a quick cookbook of how I did it; I'll update this post with additional useful examples as I encounter them.

Installation

In most *nix distributions, you can install by simple typing

sudo cpan -i "XML::Twig"

Answer 'y' when Cpan asks you if you want to install other packages that xml_grep depends on.

(xml_grep is included as a part of the XML::Twig package in CPAN. XML::Twig also provides a rich Perl API; however the command line examples here do not require any Perl knowledge.)

Usage examples

Suppose you have the following xml:

XML:
  1. <?xml version="1.0" ?>                                                                                                                     
  2. <Events>                                                                                                                                           
  3.   <Event ID="0E0042D2CF2E9E44">                                                                                                           
  4.     <ArtistIDs>                                                                                                                           
  5.       <ArtistID ID="806533" Type="Primary"/>                                                                                               
  6.       <ArtistID ID="1134688" Type="Secondary"/>                                                                                           
  7.     </ArtistIDs>
  8.     <EventDate>2009-07-25</EventDate>                                                                                           
  9.     <PerformanceName>Summer Splash 2009</PerformanceName>                                               
  10.   </Event>       
  11.    <!-- etc -->
  12. </Events>

xml_grep allows you to specify both --root (the node to match and print out) and --cond (an xpath expression relative to root that filters the results.) If more than one root is provided, the results are combined using OR; likewise with cond.

1. Here's the simplest possible example. --root is an xpath expression specifying which nodes will be printed. -p causes the results to be pretty printed:

Print all events : xml_grep -p --root="Event" foo.xml

2. The next example shows how we can use --cond to filter the results based on an attribute of a subnode of the root.

Print all events for a given artist: xml_grep -p --root="Event" --cond='ArtistID[@ID='806533']' foo.xml

3. Here's how we can match against the text contents of a subelements:

Print all events on a given date: xml_grep -p --root="Event" --cond='EventDate[string()="2009-07-25"]' foo.xml

Multiple cond flags are combined using OR. To print nodes matching one of several artists:

4.Print all events for a given artist: xml_grep -p --root="Event" --cond='ArtistID[@ID='806533']' --cond='ArtistID[@ID='806532']' foo.xml

5. The result of xml_grep is itself an XML document and can be piped into another xml_grep to do additional extraction.

Extract the time and performance name of events on a given date: xml_grep -p --root="Event" --cond='EventDate[string()="2009-07-25"]' foo.xml | xml_grep --root='EventTime' --root='PerformanceName'

5. The result of xml_grep is itself an XML document and can be piped into another xml_grep to do additional extraction.

6. Match a regular expression: xml_grep --root="EventName[string() =~ /Bil.* El.*/]" foo.xml

Tutorial: Opensocial Data Pipelining and Templates in the iLike Profile view on Orkut

Early this year, the Orkut team became aware that "a small subset of OpenSocial applications [were] being used to spread phishing attacks to Orkut users."

To solve this, the Orkut team phased out support for Flash and Javascript in profile views and required that app developers use Opensocial data pipelining and templates to specify profile markup.

Data pipelining is declarative way to specify a set of REST calls to fetch the JSON data for a page, these include both OpenSocial and arbitrary REST calls. Templates are an HTML markup language (akin to JSTL or RHTML) that is interpreted on the Opensocial servers to generate the page markup. It includes simple expressions, conditional, and looping constructs, bound against the JSON data from data pipelining.

Orkut is currently the only container that has implemented these features, which are are expected to be part of the Opensocial 0.9 spec.

The specs for these features are still evolving and some of the online tutorials and wiki entries are inconsistent and no longer work. I thought it might be helpful to document an working example (as of August 2009) based on the iLike profile view.

The first step is to require the opensocial-data and opensocial-templates features in the ModulePrefs.

XML:
  1. <Module>
  2.   <ModulePrefs>
  3.     <Require feature="opensocial-data"/>
  4.     <Require feature="opensocial-templates">
  5.       <Param name="process-on-server">true</Param>
  6.     </Require>

Next we declare the profile content...

XML:
  1. <Content type="html" view="profile">

...and specify the JSON requests required by the profile view using the os:HttpRequest declaration:

XML:
  1. <script type="text/os-data" xmlns:os="http://ns.opensocial.org/2008/markup">
  2.   <os:HttpRequest key="cache" authz="signed" href="http://philbo.dev.ilike.com/gadget/ilike_async_get_cache_key" format="json" signViewer="false" params="orkut_profile=true"/>   
  3.   <os:HttpRequest key="profile" authz="signed" href="http://philbo.dev.ilike.com/gadget/profile_tracks_json" params="synd=orkut&key=${cache.content.key}" format="json" signViewer="false"/> 
  4. </script>

The ilike_async_get_cache_key request returns JSON like this: {"key": 139222}.

The profile_tracks_json request returns JSON like this:
{"tracks":[{,"artist_name":"Bouncing Souls","name":"The Pizza Song"}, {"artist_name":"The Aquabats","name":"Pizza Day"}],"track_count":2}}.

Note how the second request includes query string parameters (${cache.content.key}) derived from the JSON data in the first request. In this case, the profile_tracks_json is set to have a very long cache lifetime; the key is a profile timestamp use to force a fresh version of the profile to be fetched when the profile changes. (The timestamp is maintained in memached and can be returned very quickly by the server.)

It is poorly documented that Orkut's os:HttpRequest wraps this content in a "content" hash wrapper, which is why we have to say "cache.content.key".

Now that we have the data, we declare the template that binds to the json data to generate the markup. Note the use of "if" and "repeat" attributes for conditionals and loops and the "${}" expressions; also note the required use of osx:NavigateToApp to link to canvas pages.

XML:
  1. <script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup" xmlns:osx= "http://ns.opensocial.org/2009/extensions">
  2.     <div id="profile_songs">
  3.       <div class="gadget_profile_header">
  4.         <osx:NavigateToApp params="{path:&quot;songs_ilike&quot;}">
  5.         See All
  6.         </osx:NavigateToApp>
  7.       <b>Songs iLike</b>
  8.     </div>
  9.  
  10.     <div if="${profile.content.track_count == 0}">
  11.       No songs
  12.     </div>
  13.  
  14.     <div  if="${profile.content.track_count> 0}" >
  15.       <ul>
  16.         <li repeat="${profile.content.tracks}" var="track">
  17.             <osx:NavigateToApp params="{&quot;track_name&quot;:&quot;${track.name}&quot;,&quot;artist_name&quot;:&quot;${track.artist_name}}">
  18.               <span class="song_title">
  19.                   ${track.name}
  20.               </span>
  21.             </osx:NavigateToApp>           
  22.         </li>
  23.       </ul>
  24.     </div>
  25.    </div>
  26. </script>

In our case, the template is itself generated using an RHTML template in the Rails framework. We can use Ruby expressions to help simplify generation of the static template, but not, of course, any of the dynamic content or conditionals that depend upon the JSON data.

For example, the grungy escaped NavigateToApp parameters are actually generated by this more readable RHTML code:

XML:
  1. <osx:NavigateToApp params="<%= escape_json('path' => 'track_page', 'autoplay' => true, 'artist_name' => '${track.artist_name}', 'track_name' => '${track.name}') %>">