Indelible InkFresh Ink (Android)2024-02-06T16:01:44+00:00https://www.indelible.org/ink/Jon PariseCopyright 1999-2024 by Jon Parise. All rights reserved.Android Text Links Using Linkify2010-04-09T00:00:00+00:00https://www.indelible.org/ink/android-linkifyJon Parise<p>The Android framework provides an easy way to automatically convert text
patterns into clickable links. By default, Android knows how to recognize web
URLs, email addresses, map addresses, and phone numbers, but it also includes
a flexible mechanism for recognizing and converting additional text patterns,
as well.</p>
<p>The <a href="http://android-developers.blogspot.com/">Android Developers Blog</a> has an article entitled <a href="http://android-developers.blogspot.com/2008/03/linkify-your-text.html">Linkify your
Text!</a> that provides a nice overview of the system. It discusses how the
<a href="http://developer.android.com/reference/android/text/util/Linkify.html">Linkify class</a> can be used to enable the default link patterns and
then continues with a more advanced <a href="http://c2.com/cgi/wiki?WikiWord">WikiWords</a> example that demonstrates
custom links. That article is a fine introduction to the system, so the rest
of this article will primarily focus on details not covered therein.</p>
<p>All of the examples in this article are based on the <a href="http://developer.android.com/reference/android/widget/TextView.html">TextView</a> widget. The
<code class="language-plaintext highlighter-rouge">Linkify</code> class can also be used to add links to <a href="http://developer.android.com/reference/android/text/Spannable.html">Spannable</a> text, but those
use cases won’t be covered here because their usage is nearly identical to the
TextView cases.</p>
<h2 id="textview-autolinking">TextView AutoLinking</h2>
<p>The TextView widget features an <a href="http://developer.android.com/reference/android/widget/TextView.html#attr_android:autoLink"><code class="language-plaintext highlighter-rouge">android:autoLink</code></a> attribute that
controls the types of text patterns that are automatically recognized and
converted to clickable links. This attribute is a convenient way to enable
one or more of the default link patterns because it can be configured directly
from a layout without involving any additional code.</p>
<p>However, for those cases where programmatically setting this value is useful,
the <a href="http://developer.android.com/reference/android/widget/TextView.html#setAutoLinkMask(int)"><code class="language-plaintext highlighter-rouge">setAutoLinkMask()</code></a> function exists.</p>
<p>There is one important caveat to using this “auto-linking” functionality,
however. It appears that when “auto-linking” is enabled, all additional
Linkify operations are ignored. It’s unclear whether this behavior is
intentional or inadvertent, so it’s possible things could change in future
released of the Android SDK. Consider disabling “auto-linking” before using
any of the Linkify operations discussed below.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// Disable the text view's auto-linking behavior</span>
<span class="n">textView</span><span class="o">.</span><span class="na">setAutoLinkMask</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> </code></pre></figure>
<h2 id="default-link-patterns">Default Link Patterns</h2>
<p>Enabling support for one of Android’s default link patterns is very easy.
Simply use the <a href="http://developer.android.com/reference/android/text/util/Linkify.html#addLinks(android.widget.TextView,%20int)"><code class="language-plaintext highlighter-rouge">addLinks(TextView text, int mask)</code></a> function and
specify a mask that describes the desired link types.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">android.text.util.Linkify</span><span class="o">;</span>
<span class="c1">// Recognize phone numbers and web URLs</span>
<span class="nc">Linkify</span><span class="o">.</span><span class="na">addLinks</span><span class="o">(</span><span class="n">text</span><span class="o">,</span> <span class="nc">Linkify</span><span class="o">.</span><span class="na">PHONE_NUMBERS</span> <span class="o">|</span> <span class="nc">Linkify</span><span class="o">.</span><span class="na">WEB_URLS</span><span class="o">);</span>
<span class="c1">// Recognize all of the default link text patterns </span>
<span class="nc">Linkify</span><span class="o">.</span><span class="na">addLinks</span><span class="o">(</span><span class="n">text</span><span class="o">,</span> <span class="nc">Linkify</span><span class="o">.</span><span class="na">ALL</span><span class="o">);</span>
<span class="c1">// Disable all default link detection</span>
<span class="nc">Linkify</span><span class="o">.</span><span class="na">addLinks</span><span class="o">(</span><span class="n">text</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span></code></pre></figure>
<h2 id="custom-link-patterns">Custom Link Patterns</h2>
<p>Detecting additional types of link patterns is easy, too. The
<a href="http://developer.android.com/reference/android/text/util/Linkify.html#addLinks(android.widget.TextView,%20java.util.regex.Pattern,%20java.lang.String)"><code class="language-plaintext highlighter-rouge">addLinks(TextView text, Pattern pattern, String scheme)</code></a>
function detects links based on a regular expression pattern.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.util.regex.Pattern</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.text.util.Linkify</span><span class="o">;</span>
<span class="c1">// Detect US postal ZIP codes and link to a lookup service</span>
<span class="nc">Pattern</span> <span class="n">pattern</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"\\d{5}([\\-]\\d{4})?"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">scheme</span> <span class="o">=</span> <span class="s">"http://zipinfo.com/cgi-local/zipsrch.exe?zip="</span><span class="o">;</span>
<span class="nc">Linkify</span><span class="o">.</span><span class="na">addLinks</span><span class="o">(</span><span class="n">text</span><span class="o">,</span> <span class="n">pattern</span><span class="o">,</span> <span class="n">scheme</span><span class="o">);</span></code></pre></figure>
<p>The text is scanned for pattern matches. Matches are converted to links that
are generated by appending the matched text to the provided URL scheme base.</p>
<p>Note that the scheme doesn’t have to be an external web-like URL. It could
also be an Android Content URI that can be used in conjunction with a <a href="http://developer.android.com/guide/topics/providers/content-providers.html">content
provider</a> to reference application resources, for example.</p>
<h2 id="match-filters">Match Filters</h2>
<p>Regular expressions are a very powerful way to match text patterns, but
sometimes a bit more flexibility is needed. The <a href="http://developer.android.com/reference/android/text/util/Linkify.MatchFilter.html">MatchFilter</a> class
provides this capability by giving user code a chance to evaluate the link
worthiness of some matched text.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.util.regex.Pattern</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.text.util.Linkify</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.text.util.Linkify.MatchFilter</span><span class="o">;</span>
<span class="c1">// A match filter that only accepts odd numbers.</span>
<span class="nc">MatchFilter</span> <span class="n">oddFilter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MatchFilter</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="nf">acceptMatch</span><span class="o">(</span><span class="nc">CharSequence</span> <span class="n">s</span><span class="o">,</span> <span class="kt">int</span> <span class="n">start</span><span class="o">,</span> <span class="kt">int</span> <span class="n">end</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="nc">Character</span><span class="o">.</span><span class="na">digit</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="n">end</span><span class="o">-</span><span class="mi">1</span><span class="o">),</span> <span class="mi">10</span><span class="o">);</span>
<span class="k">return</span> <span class="o">(</span><span class="n">n</span> <span class="o">&</span> <span class="mi">1</span><span class="o">)</span> <span class="o">==</span> <span class="mi">1</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="c1">// Match all digits in the pattern but restrict links to only odd</span>
<span class="c1">// numbers using the filter.</span>
<span class="nc">Pattern</span> <span class="n">pattern</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"[0-9]+"</span><span class="o">);</span>
<span class="nc">Linkify</span><span class="o">.</span><span class="na">addLinks</span><span class="o">(</span><span class="n">text</span><span class="o">,</span> <span class="n">pattern</span><span class="o">,</span> <span class="s">"http://..."</span><span class="o">,</span> <span class="n">oddFilter</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span></code></pre></figure>
<p>A more complex (but useful!) example would involve matching valid dates. The
regular expression could be generous enough to match strings like “2010-02-30”
(February 30, 2010), but a match filter could provide the logic to reject
bogus calendar dates.</p>
<h2 id="transform-filters">Transform Filters</h2>
<p>Up until this point, the final link was always being generated based on the
exact matched text. There are many cases where that is not desirable,
however. For example, it’s common to mention a username using the <code class="language-plaintext highlighter-rouge">@username</code>
syntax, but the resulting link should only include the <code class="language-plaintext highlighter-rouge">username</code> portion of
the text. The <a href="http://developer.android.com/reference/android/text/util/Linkify.TransformFilter.html">TransformFilter</a> class provides a solution.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">java.util.regex.Pattern</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.text.util.Linkify</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.text.util.Linkify.TransformFilter</span><span class="o">;</span>
<span class="c1">// A transform filter that simply returns just the text captured by the</span>
<span class="c1">// first regular expression group.</span>
<span class="nc">TransformFilter</span> <span class="n">mentionFilter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TransformFilter</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="nc">String</span> <span class="nf">transformUrl</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Matcher</span> <span class="n">match</span><span class="o">,</span> <span class="nc">String</span> <span class="n">url</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">match</span><span class="o">.</span><span class="na">group</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="c1">// Match @mentions and capture just the username portion of the text.</span>
<span class="nc">Pattern</span> <span class="n">pattern</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"@([A-Za-z0-9_-]+)"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">scheme</span> <span class="o">=</span> <span class="s">"http://twitter.com/"</span><span class="o">;</span>
<span class="nc">Linkify</span><span class="o">.</span><span class="na">addLinks</span><span class="o">(</span><span class="n">text</span><span class="o">,</span> <span class="n">pattern</span><span class="o">,</span> <span class="n">scheme</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">mentionFilter</span><span class="o">);</span></code></pre></figure>
<p>This approach uses the regular expression’s capture syntax to extract just the
username portion of the pattern as a uniquely addressable match group.
Alternatively, the transform filter could just return all of the matched text
after the first character (<code class="language-plaintext highlighter-rouge">@</code>), but the above approach is nice because it
keeps all of the pattern’s details within the regular expression.</p>
<p>Of course, transform filters can be combined with match filters for ultimate
flexibility. The Android SDK uses this approach to detect wide ranges of
phone number formats (many of which include various parentheses and dashes)
while always generating a simplified link containing only digits.</p>
<h2 id="further-reading">Further Reading</h2>
<p>For more information about the specific implementation details of Android’s
link generation system, the best reference is actually the source code itself.
In addition to being a good resource for understanding the system, it’s also
the best way to track down potential bugs or misunderstandings about how the
system is intended to be used.</p>
<ul>
<li><a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/android/text/util/Linkify.java">Linkify.java</a> - The <code class="language-plaintext highlighter-rouge">Linkify</code> class itself, including the <code class="language-plaintext highlighter-rouge">MatchFilter</code>
and <code class="language-plaintext highlighter-rouge">TransformFilter</code> implementations for the standard link types.</li>
<li><a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/android/text/util/Regex.java">Regex.java</a> - A collection of regular expressions and utility functions
used by <code class="language-plaintext highlighter-rouge">Linkify</code> to work with the standard link types.</li>
</ul>