Sunday, September 15, 2013

Android Twitter API 1.1 App

On June 11, 2013, Twitter turned off version 1.0 of their REST API. The API had been deprecated for quite sometime but still a lot of people were caught by surprise. All over the Internet a crap load of tutorials broke. Twitter was always a favorite for tutorial writers, since their servers were open and always had fresh data. Quite frankly, I was surprised how long Twitter let everyone and anyone use their servers. It was incredibly generous and must have been equally expensive. 

Now the free ride isn't over. Twitter just wants to know who is using their servers. And they are using OAuth to do it. Now OAuth can be a bit tricky to work with, not overly painful, but tricky. It is also a bit of overkill if all you want to do is something as simple as grabbing a user's public timeline. Luckily, there is an easier alternative: Application-Only Authentication.

With application-only authentication, your app is able to make authenticated requests its own. It doesn't need a user's credentials. Now, it can't do everything. Since it is doesn't have a user context, it isn't able to do things like status updates. But it can do things like grabbing a user's timeline, which is what this tutorial will do. 


YOU WILL NEED YOUR OWN CONSUMER KEY & SECRET!

I want to say that up front to hopefully stop people from complaining that the app doesn't work later. Getting a key and secret is easy and free so there is no good reason for not getting one. The app doesn't have a working key or secret. It will build, but it won't run until you replace the mock key and secret, with real ones. 


Getting Your Own Consumer Key and Secret
  1. Simply go to https://dev.twitter.com
  2. Click the "Sign in" link in the upper right hand corner
  3. Enter your credentials and click "Log in"
  4. Hover over your avatar in the upper right hand corner
  5. Click the "My applications" link
  6. Click the "Create a new application" button 
  7. Fill out the application details
  8. Accept the "Developer Rules of the Road"
  9. Complete the captcha
  10. Click the "Create your Twitter application" button
  11. On the "My applications" page, click on your application's name
  12. Your consumer key and secret will listed in the OAuth settings section




Keep your consumer key and secret secure. Don't do anything foolish with them, like publish them in a tutorial.


Android's Listview Adapter and AsyncTask

This app uses a quick and simply listview to render the tweets. It looks absolutely ugly, but this tutorial is about getting access to a user's timeline in API 1.1, not how to render pretty listview in Android. There are already a lot of tutorials out there on how to that. 

We also make use of one of Android's cooler features, AsyncTasks. I hope that we all know by now, that we aren't suppose to do anything which takes a long time on the main UI thread, you know things like calling web services. The AsyncTask is probably the easiest way to avoid getting an ANR. And it is so dead simple to use. 


The app uses a private class, DownloadTwitterTask which inherits from AsyncTask. Its job is to download the current batch of tweets on the user's timeline. The doInBackground method does all of the grunt work. It grabs the user's screen name, and calls the getTwitterStream method. This is what makes the AsyncTask class cool. You don't have to think about multi-tasking or other such complexity, just what is long running task you want to do, and do it. 

Once your task is complete, return your object and AsyncTask will pass it on to the onPostExecute method which runs on the UI thread. This is the perfect time to update your UI. Now for us the string passed to the onPostExecute method is the user's timeline in JSON format, so we convert it to a Twitter object which we define as an array list of Tweets. We use Google's excellent Gson library to do that conversion and a few others. And finally we feed our tweets to the list adapter for rendering.



Getting Authenticated and a User's Timeline

We have rendered our tweets to our list view, but how did we get them? Once you have your key and secret, it is surprisingly simple to get to a user's timeline, just make a few HTTPS calls. In fact, Twitter lists out how to do it in three steps, well OK there are some sub-steps. The getTwitterStream method lists out all of the steps necessary to authenticate your app and then get some tweets. 

Step 1: Encode consumer key and secret

First, we need to URL encode the consumer key and secret. Java has this function built-in, URLEncoder, be sure to pass your encoding. Then we concatenate the encoded strings with a semi-colon separating them and Base64 encode the whole thing. This is the one spot where I ran into some trouble. I wasn't sure what flag to pass. I initially tried, Base64.DEFAULT, which didn't work. Luckily there weren't that many options, when I tried Base64.NO_WRAP, it worked. 

Step 2: Obtain a bearer token

In order to get the bearer token, you need to make a post request to the Twitter token URL. With the request, you need to pass a few header keys. If each of the keys isn't correct, your request will be rejected. Also the request body must only contain "grant_type=client_credentials", or your request will be rejected. 

If everything went well, you will now have your bearer token. Be sure to check it as well. Its token should equal "bearer". 

Step 3: Authenticate API requests with bearer token

With our token, we can now make API requests. Simply set the Authorization header equal to the string "Bearer " plus our access token. For both the Http Post and the Http Get, I use the same method getResponseBody to read the entire HTTP stream and return it as a string. Everywhere, if we encounter an exception, no attempt is made to recover, we usually return an empty string or a null.

Summary

Again, please don't forget to replace the dummy consumer key and secret with your own. The code will not work without it. The project is in IntelliJ IDEA format. I just can't bring myself to use Eclipse. If you use Eclipse, simply copy the files into your project. Please feel free to use the code however you'd like. 

Resources






14 comments:

  1. I'm a community blog curator for DZone. I wanted to talk with you about potentially featuring your blog on DZone's content portals. Send me an email at alecn@DZone.com and I'll explain the details.

    ReplyDelete
  2. Hello Troy, First of all thanks for this nice tutorial.

    I tried this tutorial the same as you explained. But I get a RunTimeError. I have no idea how to solve. Can you please help me out?

    This is the error:

    Authentication error: Unable to respond to any of these challenges: {}
    W/dalvikvm(4444): threadid=11: thread exiting with uncaught exception (group=0x41b5a2a0)
    E/AndroidRuntime(4444): FATAL EXCEPTION: AsyncTask #
    E/AndroidRuntime(4444): java.lang.RuntimeException: An error occured while executing doInBackground()
    E/AndroidRuntime(4444): at android.os.AsyncTask$3.done(AsyncTask.java:299)
    E/AndroidRuntime(4444): at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
    E/AndroidRuntime(4444): at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
    E/AndroidRuntime(4444): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
    E/AndroidRuntime(4444): at java.util.concurrent.FutureTask.run(FutureTask.java:137)

    E/AndroidRuntime(4444): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
    E/AndroidRuntime(4444): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
    E/AndroidRuntime(4444): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
    E/AndroidRuntime(4444): at java.lang.Thread.run(Thread.java:856)
    E/AndroidRuntime(4444): Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column
    E/AndroidRuntime(4444): at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:176)
    E/AndroidRuntime(4444): at com.google.gson.Gson.fromJson(Gson.java:803)
    E/AndroidRuntime(4444): at com.google.gson.Gson.fromJson(Gson.java:768)
    E/AndroidRuntime(4444): at com.google.gson.Gson.fromJson(Gson.java:717)
    E/AndroidRuntime(4444): at com.google.gson.Gson.fromJson(Gson.java:689)
    E/AndroidRuntime(4444): at com.example.TwitterTutorial.MainActivity$DownloadTwitterTask.jsonToAuthenticated(MainActivity.java:107)

    E/AndroidRuntime(4444): at com.example.TwitterTutorial.MainActivity$DownloadTwitterTask.getTwitterStream(MainActivity.java:166)
    E/AndroidRuntime(4444): at com.example.TwitterTutorial.MainActivity$DownloadTwitterTask.doInBackground(MainActivity.java:67)
    E/AndroidRuntime(4444): at com.example.TwitterTutorial.MainActivity$DownloadTwitterTask.doInBackground(MainActivity.java:1)
    E/AndroidRuntime(4444): at android.os.AsyncTask$2.call(AsyncTask.java:287)
    E/AndroidRuntime(4444): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
    E/AndroidRuntime(4444): ... 5 more
    11-
    E/AndroidRuntime(4444): Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column
    E/AndroidRuntime(4444): at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:374)
    E/AndroidRuntime(4444): at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:165)
    E/AndroidRuntime(4444): ... 15 more
    11-12 14:30:10.220: I/Process(4444): Sending signal. PID: 4444 SIG: 9



    Please let me know. Thanks in advance!

    ReplyDelete
    Replies
    1. Hello
      I have followed the above procedure and created my keys for the application but i am not able to get the tweets. My code is not working. I have tried everything but i am not able toe get the tweets, i am stuck in between of a application.Please can you send me the whole source code, my id is mukesh17m@gmail.com, For this i will be very thankful to you..

      Delete
    2. I am unsure what you mean by "whole source code" The entire source code is on GitHub, with the exception of my keys. I can't let you have my keys for obvious reasons. You need to get the app working with your own keys.

      Delete
    3. All -
      The error above is caused by not replacing the CONSUMER_KEY and CONSUMER_SECRET values with valid ones provided by twitter. Please follow the steps at the beginning of the article to obtain the keys.

      Delete
  3. Just a note to say excellent tutorial - thanks for taking the time to put all this together so the rest of us can leverage your hard work.

    ReplyDelete
  4. great article, this looking to use the new api twiter to continue my study android.
    many, many thanks.
    success for you

    ReplyDelete
    Replies
    1. Thanks for reading. More great stuff to come.

      Delete
  5. Hey Troy. I tried executing your code. I get a Runtime Exception just like the one mentioned by @androidBeginner.

    java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column

    Any ideas why this might be occuring ?

    ReplyDelete
    Replies
    1. Hi amarnath,

      When I remove my valid keys from the application I get the same error that you and @androidbeginner indicate.

      Did you get application keys from twitter? The keys provided in the application are not valid. Keys are secret things which the article shows how to get your own.

      Please confirm that you have gotten your own keys.

      Thanks,

      Troy

      Delete
  6. Hey Troy,

    Can this work with Twitter's Streaming API? I'm making an app & I want to be able to search/monitor a twitter account for keywords - and when the twitter account sends out a tweet that matches the keywords, it will open a webpage link that is in that tweet.

    ReplyDelete
    Replies
    1. Hi John,
      I am a bit confused. The app does use Twitter's Streaming API. In the example shown it is retrieving from my stream, but that can be changed to anyone's stream. You would have to change the app so that it polls instead of simply calling once. And then add code to scan each tweet.

      Please feel free to ask if you more questions.

      Bye,

      Troy

      Delete
    2. How do I change the app so that it polls? Also what is the code to scan each tweet?

      Any info/help/tutorial would be MUCH appreciated!

      Delete
  7. Top 10 Marketplace (websites) where you can buy android app source code. - http://gentleninja.com/blog/buyandroidappsourcecode/

    ReplyDelete