Android Development Tutorial – A Simple Twitter Feed Reader

Posted: 30/03/2013 in Android

 

 

 

Last week I demonstrated designing and implementing customized layouts for Android ListView items, including some simple example code. This time around I’d like to expand on that code to make something a little closer to an actual app, illustrating some new tools and concepts in the process such as working with JSON data and the Android BitmapFactory.

In this post I’ll show you how to implement a Twitter feed reader for Android. Let’s start by familiarizing ourselves with the Twitter Search API Say we wanted to see what Twitter users have to say about Android today. A Twitter API search query would look something like:

http://search.twitter.com/search.json?q=@android

Twitter’s Search API allows up to 100 results to be returned per query, which is more than enough for our example today, but which introduces some interesting challenges as we want to display more data in a user-friendly fashion – more on that later. For today’s purposes, I’ll restrict return data to 25 results, using the following query:

http://search.twitter.com/search.json?q=@android&rpp=25&page=1

The “page” parameter can be incremented to step through chunks of 25 results in sequential queries. Go ahead and click on that link (will open a new window/tab) to check out the JSON data returned. There’s a lot of data values in there, but for simplicity we’ll be focusing on the following values:

  • from_user
  • text
  • profile_image_url

I’ll expand the simple example app from my last post to display twitter posts instead of a hard-coded list of users. The layout from the previous tutorial can be easily repurposed by changing the icon to the Twitter user’s avatar, placing the twitter username in the “username” field, and placing the tweet in the message field. Our goal is something like:

So how do we get there? Let’s go through it, step by step. First, we edit the listitem.xml file to give the View components more helpful names/IDs:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android&#8221;
 android:layout_height=”wrap_content” 
 android:gravity=”left|center”
 android:layout_width=”wrap_content” 
 android:paddingBottom=”5px”
 android:paddingTop=”5px” 
 android:paddingLeft=”5px”>

  <ImageView
   android:id=”@+id/avatar”
   android:layout_width=”wrap_content”
   android:layout_height=”fill_parent”
   android:layout_marginRight=”6dip”
   android:src=”@drawable/icon” />
  <LinearLayout
   android:orientation=”vertical”
   android:layout_width=”0dip”
   android:layout_weight=”1″
   android:layout_height=”fill_parent”>
    <TextView android:id=”@+id/username”
     android:layout_width=”wrap_content”
     android:layout_height=”wrap_content”
     android:gravity=”center”/>
    <TextView android:id=”@+id/message” 
     android:layout_width=”wrap_content” 
     android:layout_height=”wrap_content”
     android:layout_marginLeft=”10px” 
     android:textColor=”#0099CC”/>
  </LinearLayout>
</LinearLayout>

We can keep the main.xml from the previous post, since all we are changing is the layout within each item. Now we need some workhorse code to manage calling the Twitter API and formatting the return data for easy use. Let’s wrap our returned tweets in a simple Tweet class:

public class Tweet {
  public String username;
  public String message;
  public String image_url;

  public Tweet(String username, String message, String url) {
    this.username = username;
    this.message = message;
    this.image_url = url;
  }
}

I’ve also written a simple getTweets(searchTerm, page) method to generate the API call and process the return data into our Tweet objects. Here goes:

public ArrayList&lt;Tweet> getTweets(String searchTerm, int page) {
  String searchUrl = 
        “http://search.twitter.com/search.json?q=@” 
        + searchTerm + “&rpp=100&page=” + page;&lt;/p>

  ArrayList<Tweet> tweets = 
        new ArrayList<Tweet>();

  HttpClient client = new  DefaultHttpClient();
  HttpGet get = new HttpGet(searchUrl);

  ResponseHandler&lt;String> responseHandler = 
        new BasicResponseHandler();

  String responseBody = null;
  try {
    responseBody = client.execute(get, responseHandler);
  } catch(Exception ex) {
    ex.printStackTrace();
  }

  JSONObject jsonObject = null;
  JSONParser parser=new JSONParser();

  try {
    Object obj = parser.parse(responseBody);
    jsonObject=(JSONObject)obj;
  }catch(Exception ex){
    Log.v(“TEST”,”Exception: ” + ex.getMessage());
  }

  JSONArray arr = null;

  try {
    Object j = jsonObject.get(“results”);
    arr = (JSONArray)j;
  } catch(Exception ex){
    Log.v(“TEST”,”Exception: ” + ex.getMessage());
  }

  for(Object t : arr) {
    Tweet tweet = new Tweet(
      ((JSONObject)t).get(“from_user”).toString(),
      ((JSONObject)t).get(“text”).toString(),
      ((JSONObject)t).get(“profile_image_url”).toString()
    );
    tweets.add(tweet);
  }

  return tweets;
}

The code above is using the json-simple library, a nice lightweight tool for JSON encoding and decoding. I don’t have time now, but I may do some demos/examples using json-simple in a later post if there is interest. Aside from exception handling, all the code above does is formats the Twitter search request url from the input parameters “searchTerm” and “page”, makes an HTTP GET request using the generate url, parses the JSON return value, and stuffs the data into Tweet objects.

Notice that the Twitter user avatar data (profile_image_url) returned is simply a url to the user’s avatar. We need one more processing step to get the actual image, since Android ImageViews can’t display an image directly from the web without downloading it. (good thing too, or offline usage would be quite dull!) Let’s see how few lines we can do this in:

public Bitmap getBitmap(String bitmapUrl) {
  try {
    URL url = new URL(bitmapUrl);
    return BitmapFactory.decodeStream(url.openConnection().getInputStream()); 
  }
  catch(Exception ex) {return null;}
}

May not break any records, but the code above is fairly compact. It uses another handy Android API tool called the BitmapFactory. The BitmapFactory lives and breathes bitmaps, its sole purpose in life being to return a bitmap from whatever source data you give it. File, filestream, byte array, you name it. Because of this, I don’t have to put any thought at all into bits, bytes, files, http requests, or anything else to accomplish this task. Rock on, BitmapFactory.

Now we have all of the tools we need to refactor the ItemAdapter from last week’s post to display our Tweet objects. Here we go:

public class TweetItemAdapter extends ArrayAdapter<Tweet> {
  private ArrayList<Tweet> tweets;

  public TweetItemAdapter(Context context, int textViewResourceId, ArrayList<Tweet> tweets) {
    super(context, textViewResourceId, tweets);
    this.tweets = tweets;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View v = convertView;
    if (v == null) {
      LayoutInflater vi = 
         (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      v = vi.inflate(R.layout.listitem, null);
    }

    Tweet tweet = tweets.get(position);
    if (tweet != null) {
      TextView username = (TextView) v.findViewById(R.id.username);
      TextView message = (TextView) v.findViewById(R.id.message);
      ImageView image = (ImageView) v.findViewById(R.id.avatar);

      if (username != null) {
        username.setText(tweet.username);
      }

      if(message != null) {
        message.setText(tweet.message);
      }

      if(image != null) {
        image.setImageBitmap(getBitmap(tweet.image_url));
      }
    }

    return v;
  }
}

Not too many changes from the original, but the changes we have made are important. Let’s go through them. No conceptual changes in the constructor, so I’ll skip that. getView() is where the magic happens. Now, since we are setting the avatar image independently for each user, we are grabbing an ImageView object as well as two TextView objects from our View. Since we already parsed the Twitter JSON data in the getTweets() method and packaged it nicely into our Tweet objects, all we need to do here is put the extracted values from the relevant Tweet object into the text field for our TextView objects. For the ImageView, we need to set a bitmap value, which is exactly what we wrote the getBitmap(url) method to return. That’s it!

For the sake of completeness, here is the onCreate() method for this example – this illustrates setting the adapter and passing some data to the ListView as our app is launched.

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);&lt;/p>

  ArrayList<Tweet> tweets = getTweets(“android”, 1);

  ListView listView = (ListView) findViewById(R.id.ListViewId);
  listView.setAdapter(new TweetItemAdapter(this, R.layout.listitem, tweets));
}

For this example, we only ever get a single API call’s worth of data, a limitation I will address in a follow-up tutorial. Let’s take a screenshot of the finished product for posterity:

There you have it: a working Twitter feed reader, based on the Twitter search API. There are plenty of directions you could take this code, from allowing the user to specify search terms to changing the way data is aggregated or adding some persistent data features. If these are the types of things you are interested in, I hope this tutorial helps! Feel free to leave a comment or drop me a line if you have any questions. As always, you can download code using the links at the bottom of this post to try things out for yourself.

In my next post I’ll continue working on this Twitter app to add user scrolling detection and auto-expansion of the ListView, so we can continually add to the ListView as the user scrolls through. This is a handy trick that can be useful in many scenarios. Stay tuned!

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s