Implementing #hashtags in android application

Multiple Hashtags

Hashtags have become very popular in social networking websites and apps. But these can also be used in many other ways in your own apps, for example making searchable categories in your e-commerce app or inserting tag clouds, etc.

This example will show you how to implement hashtags in your application using Linkify. We’ll add messages with hashtags to a global list and then when the user clicks on any hashtag, we’ll show all the messages which has the selected hashtag in it.

Note: – Although this tutorial shows how to implement hashtags, it is also very useful to understand how to make any text act as a hyperlink.


Let’s start by creating a new Android application project by the name HashTags with package name com.sourabhsoni.hashtags

New Application Project

Once the project is created successfully, we’ll create the layout of our opening activity. Open activity_main.xml (or your main activity if you have changed the name) and create an EditText (to enter new message), a Button (adds message to the list on click) and a ListView (shows all the messages with clickable hashtags). Copy and paste this code into activity_main.xml:-

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<EditText
android:id="@+id/messageEditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:hint="Enter your message"
android:ems="10" >
<requestFocus />
</EditText>
<Button
android:id="@+id/addButton"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/messageEditText"
android:layout_alignParentRight="true"
android:layout_marginRight="15dp"
android:text="Add" />
<ListView
android:id="@+id/messagesListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/addButton" >
</ListView>
</RelativeLayout>

Note:- It is a good practise to save all string constants in strings.xml, but for easier understanding in this example, we’ll just write the string instead of reference to string.xml resource.

Activity_main should look something like this:-

MainActivity

Before we go ahead, let’s see what Linkify is. The Linkify class in the SDK lets us specify a regular expression to match, and a scheme to prepend. The scheme is a string which forms a Content URI (with the matched text added to it) to allow data to be looked up. For example, in our case, we want to look for a regular expression match for a hashtag i.e. a word containing letters and digits and underscore which starts with a hash. Linkify can then turn this matched word and the scheme to a URI like com.sourabhsoni.hashtags.tagdetailsactivity/#HashTag which can be used to open a new activity from a content provider. Linkify can be passed any TextView in your application, and will take care of creating the links and enabling their “clickability” for you.

We will create a custom Linkify and pass it to the TextView in our ListView. The regular expression that matches hashtag can be defined as:-

[#]+[A-Za-z0-9-_]+\b

Which effectively says “starting with one or more Hash find at least one either an uppercase letter or a lowercase letter or a digit or underscore until next word boundary (\b)”.

We also need to specify what Linkify needs to do when it finds a hashtag. To do this, we’ll specify a scheme which then be appended with matched hashtag and used as content URI. We define this scheme as

com.sourabhsoni.hashtags.tagdetailsactivity/

Now that we have some knowledge of Linkify and the regular expression to be used for finding a hashtag, let’s write the code behind the activity which adds the message typed in EditText to the list when the user taps on add button. Open MainActivity.java (or your main activity java class if you have given a different name) and write the following code:-

package com.sourabhsoni.hashtags;
import java.util.ArrayList;
import java.util.regex.Pattern;
import android.app.Activity;
import android.os.Bundle;
import android.text.util.Linkify;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
//public static variable for demo purpose
//we'll use this ArrayList in other activity
public static ArrayList<String> messagesList=new ArrayList<String>();
private ArrayAdapter<String> adapter;
private ListView messagesListView;
private EditText messageEditText;
private Button addButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//initialize variables
messagesListView = (ListView)findViewById(R.id.messagesListView);
messageEditText = (EditText)findViewById(R.id.messageEditText);
addButton = (Button)findViewById(R.id.addButton);
//Extend an ArrayAdapter so that we can make
//our TextView HashTag compatible
adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,
messagesList){
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView hashView;
if (convertView == null) {
hashView = new TextView(MainActivity.this);
} else
hashView = (TextView) convertView;
//Get the message from list and set as text
String message = messagesList.get(position);
hashView.setText(message);
//Pattern to find if there's a hash tag in the message
//i.e. any word starting with a # and containing letter or numbers or _
Pattern tagMatcher = Pattern.compile("[#]+[A-Za-z0-9-_]+\\b");
//Scheme for Linkify, when a word matched tagMatcher pattern,
//that word is appended to this URL and used as content URI
String newActivityURL = "content://com.sourabhsoni.hashtags.tagdetailsactivity/";
//Attach Linkify to TextView
Linkify.addLinks(hashView, tagMatcher, newActivityURL);
return hashView;
}
};
//assign custom adapter to messages listview
messagesListView.setAdapter(adapter);
//Add text from EditText to our messages list on button click
addButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
String message = messageEditText.getText().toString();
if(message.trim()!="")
{
messagesList.add(0,message);
adapter.notifyDataSetChanged();
messageEditText.setText("");
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}

When the user clicks on a HashTag in our app, Linkify converts them to intent links. It calls the VIEW action using the content: URI associated with the link. For Android to match the content URI, we need to define a content provider and an activity registered in the system with an intent-filter that matches both the VIEW action, and the MIME type for the data represented by the content URI. To define the content provider, create a new class called TagProvider.java and paste the following code into it.

package com.sourabhsoni.hashtags;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class TagProvider extends ContentProvider {
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri arg0) {
//MIME type of the data represented by URI
return "vnd.android.cursor.item/vnd.cc.tag";
}
@Override
public Uri insert(Uri arg0, ContentValues arg1) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
}
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
String arg4) {
// TODO Auto-generated method stub
return null;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
// TODO Auto-generated method stub
return 0;
}
}

We also need to define one more activity which will be called when a user taps on a HashTag. Create a new android layout xml file called as activity_tag_details.xml and paste the following code into it.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/messagesWithTag"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>

Add this activity and the provider class to the manifest and also specify intent filters. Your AndroidManifest.xml should look something like this:-

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sourabhsoni.hashtags"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<provider
android:name="com.sourabhsoni.hashtags.TagProvider"
android:authorities="com.sourabhsoni.hashtags.tagdetailsactivity" />
<activity
android:name="com.sourabhsoni.hashtags.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".TagDetailsActivity"
android:label = "@string/app_name">
<intent-filter >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.cc.tag" />
</intent-filter>
</activity>
</application>
</manifest>

Now, we know that when a user taps on a HashTag in any of the message, android knows which content provider to use and which activity needs to be called. In the Tag Details activity, we need will get the hashtag appended in the end of our scheme (content://com.sourabhsoni.hashtags.tagdetailsactivity/) and we need to extract that hashtag to use it for finding related messages from the global list in our MainActivity. So here is how we’ll implement the java side of our Tag details activity:-

package com.sourabhsoni.hashtags;
import java.util.ArrayList;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class TagDetailsActivity extends Activity {
//List to store messages that contain clicked Tag.
private ArrayList<String> messagesWithTag;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tag_details);
messagesWithTag=new ArrayList<String>();
ListView messagesListView=(ListView)findViewById(R.id.messagesWithTag);
//Get the content URI
Uri uri = getIntent().getData();
//strip off hashtag from the URI
String tag=uri.toString().split("/")[3];
//Iterate through all the messages and find the ones
//which contain this hashtag.
for(String message:MainActivity.messagesList)
{
if(message.contains(tag))
{
messagesWithTag.add(message);
}
}
//Show the list of messages which has the hashtag
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, messagesWithTag);
messagesListView.setAdapter(adapter);
}
}

This completes our code for implementing HashTags, let’s run it and check if it works as expected. It should look something like this:-

MainActivity_screenshot

Tag Details Screenshot

Linkify is a very useful class and can be used for many different purposes. It is heavily used in apps which need to show email addresses or phone numbers or web links. Combined with the power of content providers and regular expressions, Linkify can be used to improve user experience significantly.

Source Code

I have uploaded the entire project on github , you can download it from there and verify your code.