• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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> &mdash; 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>&lt;provider name="com.google.android.wikinotes.db.WikiNotesProvider"
90    android:authorities="com.google.android.wikinotes.db.wikinotes" /&gt;</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 &mdash; '*' &mdash; 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>&lt;intent-filter&gt;
176    &lt;action name="android.intent.action.VIEW"/&gt;
177    &lt;category name="android.intent.category.DEFAULT"/&gt;
178    &lt;category name="android.intent.category.BROWSABLE"/&gt;
179    &lt;data mimetype="vnd.android.cursor.item/vnd.google.wikinote"/&gt;
180&lt;/intent-filter&gt;</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