13 March 2008

Linkify your Text!

This is the first in a series of technical articles about WikiNotes for Android, part of the Apps for Android project.

This article covers the use of Linkify to turn ordinary text views into richer link-oriented content that causes Android intents to fire when a link is selected.

Linkify: The Linkify class in the SDK is perfect for creating a wiki note pad. This class lets you specify a regular expression to match, and a scheme to prepend. The scheme is a string that, when the matched text is added, forms a Content URI to allow the correct data to be looked up.

For example, in our case we want to look for a regular expression match for a WikiWord (that is, a word with camel case and no spaces). Linkify can then turn this into a Content URI - something like >content://com.google.android.wikinotes.db.wikinotes/wikinotes/WikiWord which can then be used to locate the correct wiki page from a content provider.

As a bonus, the Linkify class also defines several default matches, in particular it is able to turn web URLs, email addresses and telephone numbers into active links which fire Android intents automatically.

Linkify can be passed any TextView in your application, and will take care of creating the links and enabling their "clickability" for you.

Default Linkify: Using the set of default active link options is very straightforward - simply pass it a handle to a TextView with content in it, and the Linkify.ALL flag:

TextView noteView = (TextView) findViewById(R.id.noteview);
noteView.setText(someContent);
Linkify.addLinks(noteView, Linkify.ALL);

and that's it. The Linkify.ALL flag applies all of the predefined link actions, and the TextView will be immediately updated with a set of active links which, if you select them, fire default intents for the actions (e.g. a web URL will start the browser with that URL, a telephone number will bring up the phone dialer with that number ready to call, etc.).

Custom Linkify: So what about our WikiWord? There is no pre-defined action for that, so it needs to be defined and associated with a scheme.

The first task is to defined a regular expression that matches the kind of WikiWords we want to find. The regex in this case is:

\b[A-Z]+[a-z0-9]+[A-Z][A-Za-z0-9]+\b

Obvious no? Well actually this is equivalent to the following description: "Starting with a word boundary (the \b) find at least one upper case letter, followed by at least one lower case letter or a numeric digit, followed by another upper case letter, and then any mix of upper case, lower case or numeric until the next word boundary (the final \b)". Regular expressions are not very pretty, but they are an extremely concise and accurate way of specifying a search pattern.

We also need to tell Linkify what to do with a match to the WikiWord. Linkify will automatically append whatever is matched to a scheme that is supplied to it, so for the sake of argument let's assume we have a ContentProvider that matches the following content URI:

content://com.google.android.wikinotes.db.wikinotes/wikinotes/WikiWord

The WikiWord part will be appended by Linkify when it finds a match, so we just need the part before that as our scheme.

Now that we have these two things, we use Linkify to connect them up:

Pattern wikiWordMatcher = Pattern.compile("\\b[A-Z]+[a-z0-9]+[A-Z][A-Za-z0-9]+\\b");
String wikiViewURL =    "content://com.google.android.wikinotes.db.wikinotes/wikinotes/";
Linkify.addLinks(noteView, wikiWordMatcher, wikiViewURL);

Note that the \b's had to be escaped with double backslashes for the Java Pattern.compile line.

Linkify can be used multiple times on the same view to add more links, so using this after the Default Linkify call means that the existing active links will be maintained and the new WikiWords will be added. You could define more Linkify actions and keep applying them to the same TextView if you wanted to.

Now, if we have a WikiWord in the TextView, let's say MyToDoList, Linkify will turn it into an active link with the content URI:

content://com.google.android.wikinotes.db.wikinotes/wikinotes/MyToDoList

and if you click on it, Android will fire the default intent for that content URI.

For this to all work, you will need a ContentProvider that understands that Content URI, and you will need a default activity capable of doing something with the resulting data. I plan to cover these in future blog entries (and soon). In fact, the whole Wiki Note Pad application is currently undergoing some clean up and review, and will then hopefully be released as a sample application.