1page.title=WikiNotes: Routing Intents 2@jd:body 3 4 5<p>In <a href="wikinotes-linkify.html">the Linkify! article</a>, we talked about 6using Linkify to turn wiki words (those that match a regular expression that we 7have defined) into a <code>content:</code> URI and defining a path to data that 8matched a note belonging to that wiki word. As an example, a matching word like 9<code>ToDoList</code> would be turned into a URI such as 10<code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList 11</code> and then acted upon using the VIEW action from the Linkify class.</p> 12 13<p>This article examines how the Android system takes this combination of 14<code>VIEW</code> action and <code>content:</code> URI and finds the correct 15activity to fire in order to do something with the data. It will also explain 16how the other default links created by Linkify, such as web URLs and telephone 17numbers, also result in the correct activity to handle that data type being 18fired. Finally, this article will start to examine the custom 19<code>ContentProvider</code> that has been created to handle WikiNotes data. The 20full description of the ContentProvider and what it does will span a couple more 21articles as well, because there is a lot to cover.</p> 22 23<h3>The Linkify-calls-intent Workflow</h3> 24 25<p>At a high level, the steps for Linkify to invoke an intent, and for the 26resulting activity (if any) to handle it, look like this:</p> 27 28<ol> 29 <li>Linkify is invoked on a TextView to turn matching text patterns into Intent links.</li> 30 <li>Linkify takes over monitoring for those Intent links being selected by the user.</li> 31 <li>When the user selects a link, Linkify calls the VIEW action using the content: URI associated with the link.</li> 32 <li>Android takes the content: URI that represents the data, and looks for a 33ContentProvider registered in the system that matches the URI.</li> 34 <li>If a match is found, Android queries the ContentProvider using the URI, 35and asks what MIME type the data that will be returned from the URI is.</li> 36 <li>Android then looks for an activity registered in the system with an 37intent-filter that matches both the VIEW action, and the MIME type for 38the data represented by the content: URI.</li> 39 <li>Assuming a match is found, Linkify then invokes the intent for 40the URI, at which point the activity takes over, and is handed 41the content: URI.</li> 42 <li>The activity can then use the URI to retrieve the data and act on 43it.</li> 44</ol> 45 46<p>This is actually a simpler process than it 47sounds, and it is quite lightweight as well. Perhaps a more 48understandable statement about how it works might be:</p> 49 50<p>Linkify is used to turn matching text into hot-links. When the user 51selects a hot-link, Android takes the data locator represented by the 52hot-link and looks for a data handler for that data locator. If it 53finds one, it asks for what type of data is returned for that locator. 54It then looks for something registered with the system that handles 55that type of data for the VIEW action, and starts it, including the 56data locator in the request.</p> 57 58<p>The real key here is the MIME type. MIME stands for <a 59href="http://en.wikipedia.org/wiki/MIME">Multipurpose Internet Mail 60Extensions</a> — a standard for sending attachments over email. The MIME 61type (which is the part Android uses) is a way of describing certain kinds of 62data. That type is then used to look for an Activity that can do something with 63that data type. In this way, ContentProviders and Activities (or other 64IntentReceivers) are decoupled, meaning that a given Content URI might have a 65different ContentProvider to handle it, but could still use the same MIME type 66meaning that the same activity could be called upon to handle the resulting 67data.</p> 68 69<h3>Linkify on a wiki word</h3> 70 71<p>Using the above workflow, let's take a look at exactly how the process 72works in WikiNotes for Android:</p> 73 74<p>First, Linkify is used to turn text matching the wiki word regular expression 75into a link that provides a Content URI for that wiki word, for example 76<code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList</code>.</p> 77 78<p>When the user clicks on the wiki word link, Linkify invokes the VIEW 79action on the Content URI. At this point, the Android system takes over 80getting the Intent request to the correct activity.</p> 81 82<p>Next, Android looks for a ContentProvider that has been registered 83with the system to handle URIs matching our Content URI format.</p> 84 85<p>In our case, we have a definition inside 86<a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/AndroidManifest.xml">our application's AndroidManifest.xml</a> 87file that reads:</p> 88 89<pre><provider name="com.google.android.wikinotes.db.WikiNotesProvider" 90 android:authorities="com.google.android.wikinotes.db.wikinotes" /></pre> 91 92<p>This establishes that we have a ContentProvider defined in our application 93that provides the "root authority": 94<code>com.google.android.wikinotes.db.wikinotes</code>. This is the first part 95of the Content URI that we create for a wiki word link. Root Authority is just 96another way of thinking about a descriptor that is registered with Android to 97allow requests for certain URLs to be routed to the correct class.</p> 98 99<p>So, the whole definition is that a class called 100<code>com.google.android.wikinotes.db.WikiNotesProvider</code> is registered 101with the system as able to handle the 102<code>com.google.android.wikinotes.db.wikinotes</code> root authority (i.e. URIs 103starting with that identifier).</p> 104 105<p>From here, Android takes the rest of the URI and presents it to that 106ContentProvider. If you look at the 107<a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/src/com/google/android/wikinotes/db/WikiNotesProvider.java">WikiNotesProvider 108class</a> and scroll to the very bottom, in the static block there, you can see 109the pattern definitions to match the rest of the URL.</p> 110 111<p>In particular, take a look at the two lines:</p> 112 113<pre>URI_MATCHER.addURI(WikiNote.WIKINOTES_AUTHORITY, "wikinotes", NOTES); 114URI_MATCHER.addURI(WikiNote.WIKINOTES_AUTHORITY, "wikinotes/*", NOTE_NAME);</pre> 115 116<p>These are the definitions of URIs that our ContentProvider recognizes and can 117handle. The first recognizes a full URI of 118<code>content://com.google.android.wikinotes.db.wikinotes/wikinotes</code> and 119associates that with a constant called NOTES. This is used elsewhere in the 120ContentProvider to provide a list of all of the wiki notes in the database when 121the URI is requested.</p> 122 123<p>The second line uses a wildcard — '*' — to match a request of the 124form that Linkify will create, e.g. 125<code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList 126</code>. In this example, the * matches the ToDoList part of the URI and is 127available to the handler of the request, so that it can fish out the matching 128note for ToDoList and return it as the data. This also associates that match 129with a constant called NOTE_NAME, which again is used as an identifier elsewhere 130in the ContentProvider.</p> 131 132<p>The other matches in this static block are related to forms of 133searching that have been implemented in the WikiNotes for Android 134application, and will be covered in later articles. Likewise, how the 135data is obtained from this matching pattern will be the subject of the 136next article.</p> 137 138<p>For right now we are concerned with the MIME type for the URI. This is 139defined in the <code>getType()</code> method also in the 140<a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/src/com/google/android/wikinotes/db/WikiNotesProvider.java">WikiNotesProvider 141class</a> (about halfway through the file). Take a quick look at this. The key 142parts for now are:</p> 143 144<pre>case NOTES: 145 return "vnd.android.cursor.<b>dir</b>/vnd.google.wikinote";</pre> 146 147<p>and</p> 148 149<pre>case NOTE_NAME: 150 return "vnd.android.cursor.<b>item</b>/vnd.google.wikinote";</pre> 151 152<p>These are the same constant names we defined in our pattern 153matchers. In the first case, that of the all notes URI, the MIME type 154returned is <code>vnd.android.cursor.dir/vnd.google.wikinote</code> 155which is like saying an Android list (dir) of Google wiki notes (the 156vnd bit is MIME-speak for "vendor specific definition"). Likewise, in 157the case of a NOTE_NAME match, the MIME type returned is 158<code>vnd.android.cursor.item/vnd.google.wikinote</code> which is 159like saying an Android item of Google wiki notes.</p> 160 161<p>Note that if you define your own MIME data types like this, the 162<code>vnd.android.cursor.dir</code> and <code>vnd.android.cursor.item</code> 163categories should be retained, since they have meaning to the Android 164system, but the actual item types should be changed to reflect your 165particular data type.</p> 166 167<p>So far Android has been able to find a ContentProvider that handles 168the Content URI supplied by the Linkify Intent call, and has queried 169the ContentProvider to find out the MIME types for that URI. The final 170step is to find an activity that can handle the VIEW action for that 171MIME type. Take a look in the the 172<a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/AndroidManifest.xml">AndroidManifest.xml file</a> 173again. Inside the WikiNotes activity definition, you will see:</p> 174 175<pre><intent-filter> 176 <action name="android.intent.action.VIEW"/> 177 <category name="android.intent.category.DEFAULT"/> 178 <category name="android.intent.category.BROWSABLE"/> 179 <data mimetype="vnd.android.cursor.item/vnd.google.wikinote"/> 180</intent-filter></pre> 181 182<p>This is the correct combination of matches for the VIEW action on a 183WikiNote type that is requested from the LINKIFY class. The DEFAULT 184category indicates that the WikiNotes activity should be treated as a 185default handler (a primary choice) for this kind of data, and the 186BROWSABLE category means it can be invoked from a "browser", in this 187case the marked-up Linkified text.</p> 188 189<p>Using this information, Android can match up the VIEW action request 190for the WikiNotes data type with the WikiNotes activity, and can then 191use the WikiNotes activity to handle the request.</p> 192 193<h3>Why do it like this?</h3> 194 195<p>It's quite a trip through the system, and there is a lot to absorb 196here, but this is one of the main reasons I wanted to write WikiNotes 197in the first place. If you follow and understand the steps here, you'll 198have a good grasp of the whole Intents mechanism in Android, and how it 199helps loosely coupled activities cooperate to get things done.</p> 200 201<p>In this case, we could have found another way to detect wiki words 202based on a regular expression, and maybe written our own handler to 203intercept clicks within the TextView and dig out the right data and 204display it. This would seem to accomplish the same functionality just 205as easily as using intents, so what is the advantage to using the full 206Intents mechanism?</p> 207 208<p>In fact there are several advantages:</p> 209 210<p>The most obvious is that because we are using the standard Intent 211based approach, we are not limited to just linking and navigating to 212other wiki notes. We get similar behavior to a number of other data 213types as well. For example, a telephone number or web URL in a wiki 214note will be marked up by Linkify, and using this same mechanism (VIEW 215action on the linked data type) the browser or dialer activities will 216be automatically fired.</p> 217 218<p>It also means that each operation on a wiki note can be treated as a 219separate life cycle by our activity. We are not dealing with swapping 220data in and out of an existing activity - each activity works on a 221particular wiki note and that's all you have to worry about.</p> 222 223<p>Another advantage is that we now have a public activity to handle 224VIEW actions in WikiNotes no matter where the request comes from. 225Another application could request to view a wiki note (perhaps without 226even knowing what kind of data it is) and our activity could start up 227and handle it.</p> 228 229<p>The backstack is automatically maintained for you too. As you 230forward navigate through WikiNotes, Android maintains the history of 231notes visited, and so when you hit the back button you go back to the 232last note you were on. All this is free because we rely on the Android 233intents mechanism.</p> 234 235<p>Finally, if you run WikiNotes for Android and then start DDMS to 236take a look at the Activity threads in the WikiNotes application while 237it is running, you can see that despite what you might think, letting 238Android manage the navigation is very efficient. Create a few linked 239notes, as many links deep as you like, and then follow them. If you 240follow links hundreds of notes deep, you will still only see a handful 241of WikiNotes activities. Android is managing the activities, closing 242the older ones as necessary and using the life cycle to swap data in 243and out.</p> 244 245<h3>Next Time</h3> 246 247<p>This was a long article, but necessarily so. It demonstrates the 248importance of the Intents mechanism and to reinforce the notion that it 249should be used whenever possible for forward navigation, even within a 250single application. Illustrating this is one of the primary reasons I 251wrote WikiNotes for Android in the first place.</p> 252 253<p>In the next article we will look deeper into the ContentProvider and 254examine how it turns a Content URI into a row (or several rows) of data 255that can be used by an activity.</p> 256