A better way to handle links in TextView

There are two ways of “linkifying” URLs in a TextView. First, as an XML attribute:

<TextView
  ...
  android:autoLink="phone|web" />

and second, programmatically:

TextView textView = (TextView) findViewById(R.id.text1);
Linkify.addLinks(textView, Linkify.PHONE_NUMBERS | LINKIFY.WEB_URLS);

In both the cases, the framework internally registers a LinkMovementMethod on the TextView that handles dispatching a ACTION_VIEW Intent when any link is clicked. This is why phone-numbers open in a dialer when clicked, web URLs open in a browser, map URLs open in Google Maps and so on. The source can be seen in URLSpan.class (line #63):

@Override
public void onClick(View widget) {
  Uri uri = Uri.parse(getURL());
  Context context = widget.getContext();
  Intent intent = new Intent(Intent.ACTION_VIEW, uri);
  intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
  try {
    context.startActivity(intent);
  }
  ...
}

The problem with the default LinkMovementMethod is that it’s buggy and non-customizable:

1. Incorrect touch areas

It incorrectly calculates a URL’s bounds when the URL is present at any horizontal/vertical end and there’s space available in that direction inside the TextView (including its padding). This is like having ghost links in our app and that’s not good.

2. Unreliable highlighting

LinkMovementMethod highlights a URL only the first time it’s clicked and it stops working randomly after that. It also does not correctly track the touch pointer to unhighlight the URL once it’s no longer being touched.

3. No support for custom URL handling

We’re also out of luck if we want to show contextual options when a phone-number is clicked instead of simply redirecting to the dialer.

BetterLinkMovementMethod

Introducing BetterLinkMovementMethod, a.. uh.. better a version of LinkMovementMethod that solves all our problems. It’s designed to be a drop-in replacement for LinkMovementMethod:

TextView textView = (TextView) findViewById(R.id.text1);
textView.setMovementMethod(BetterLinkMovementMethod.newInstance());
Linkify.addLinks(textView, Linkify.PHONE_NUMBERS);

However, the easiest way to get started is by using one of its linkify() methods:

BetterLinkMovementMethod.linkify(Linkify.ALL, this)
    .setOnLinkClickListener((textView, url) -> {
      // Do something with the URL and return true to indicate that 
      // this URL was handled. Otherwise, return false to let Android
      // handle it.
      return true;
    })
    .setOnLinkLongClickListener((textView, url) -> {
      // Handle long-clicks.
      return true;
    });

Download and source

compile 'me.saket:better-link-movement-method:2.2.0'

BetterLinkMovementMethod is available on Github. Feel free to raise issues, send contributions or fork it for your own usage.

https://github.com/Saketme/Better-Link-Movement-Method