S. Gökhan Topçu
gtopcu@gmail.com
In a previous post, I presented sample code to post tweets to twitter using the twitter4j library. In this blog, I will be using Twitter's Rest API directly to read user's timeline and post tweets to it. Another method which is the easiest to use is the Twitter Web Intents, which are pop-up browser dialogs you can use by embedding into a WebView.
Most GET based operations using the Rest API are much easier than the POST ones, since we don't have to deal with OAuth authorizations. You can retrieve data from Twitter anonymously, but when you want to post tweets, change settings, profile parameters etc. you will need to have a valid authorization. OAuth authorization in Twitter works much like that of Facebook - user grants access to an app to do things on their behalf, and an app uses the consumer key and access token provided, from which Twitter identifies the user and the app when a call is made, respectively.
Reading user's timeline
Reading a user's timeline is pretty straightforward - in this case, the URL we will be issuing a GET request includes the user's Twitter username, and no authorization is required. The Rest API's URL for the GET statuses/user_timeline request is:Just change the username to a valid twitter username to get the example working. And don't forget to add internet permission to your manifest to be able to run these examples:
<uses-permission android:name="android.permission.INTERNET"/>
GetActivity.java
package com.gtware.android.twitter.restapi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.ListActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class GetActivity extends ListActivity {
private static final String getURL = "http://twitter.com/statuses/user_timeline/username.json";
private TextView label;
private ListView list;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.get_main);
label = (TextView)findViewById(R.id.label);
list = getListView();
try {
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(getURL);
HttpResponse response = client.execute(get);
parseGetResponse(response);
}
catch(Exception e) {
Log.e(GetActivity.class.getName(), e.getMessage());
label.setText("Error: " + e.getMessage());
}
}
private void parseGetResponse(HttpResponse response) throws IllegalStateException, IOException, JSONException, ParseException {
StringBuilder sb = getResponseBody(response);
if(response.getStatusLine().getStatusCode() == 200) {
JSONArray jsArray = new JSONArray(sb.toString());
if(jsArray.length() == 0) {
label.setText("No results");
return;
}
label.setText(jsArray.length() + " result(s):");
ArrayList<String> tweets = new ArrayList<String>();
for(int i = 0; i < jsArray.length(); i++) {
JSONObject jsObject = jsArray.getJSONObject(i);
tweets.add(jsObject.getString("created_at") + ": " + jsObject.getString("text"));
}
list.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, tweets));
}
//Not OK
else {
label.setText("Response Code: " + response.getStatusLine().getStatusCode() + "\nResponse: " + sb.toString());
Log.e(GetActivity.class.getName(), "Response Code: " + response.getStatusLine().getStatusCode() + "\nResponse: " + sb.toString());
}
}
private static StringBuilder getResponseBody(HttpResponse response) throws IllegalStateException, IOException {
InputStream is = response.getEntity().getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
return sb;
}
}
We're using Apache Http Client for our examples to issue HTTP GET/POST requests to Twitter and org.json.* package to convert result strings to JSON objects. Since both are included with the standard Android SDK, we don't need to import any third party libraries to our project. This code retrieves the most recent 20 tweets from the user @gt_ware and parses the results into a ListView. You can check out the get_timeline documentation to see all the fields returned by the Rest API.
The layout file is pretty straightforward, a TextView as a top label, a separator, and the ListView:
get_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"/>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:padding="10dp"
android:background="#0041c5"
android:paddingTop="10dp"
android:paddingBottom="10dp"/>
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
Output:
![]() |
| Figure 1: GET user_timeline using Twitter's REST API |
Posting Tweets
Before you can post tweets, you need to:1. Register a new app at Twitter Apps
2. Go to the OAuth tool section of the new app and note down the consumer/access tokens and secrets. You can also generate request-specific tokens from the OAuth tool box located on the middle-right section of the screen included at the request documentations. Using the toolbox, you can retrieve the tokens and secrets from POST statuses/update documentation for this blog specific to your app.
Once you get your tokens, you can start building the Authorization HTTP header element which Twitter will be using to authenticate the requests. For POST statuses/update request (the "tweet" request), HTTP body only needs to include a status=tweet_content, where the tweet_content is the percent encoded Tweet. At the end, your HTTP POST will need to look something like this:
POST /1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Authorization:
OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318622958",
oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
oauth_version="1.0"
Content-Length: 76
Host: api.twitter.com
status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21
The Rest API URL for the POST statuses/update request is in the form:http://api.twitter.com/1/statuses/update.json
Generation of the Authorization HTTP header is not an easy task and you need to go through:
1. Authorizing a request
2. Creating a signature
3. Percent encoding parameters
documentation to understand how the process works, but the code I'm providing below, although non-optimized for the sake of simple viewing, is a working example and handles most of the confusing HmacSHA1, percent and base64 encodings along with string construction required to build the header. It lets a single predefined user tweet from the activity, and returns the tweet's ID if the post is successful. You need to change the consumer and access tokens & secrets (the String variables consumerKey, consumerSecret, accessToken, accessTokenSecret in the code) with the ones you retrieved earlier for your own twitter app to get the example working. Also you might want to optimize the code and externalize the strings too.
PostActivity.java
package com.gtware.android.twitter.restapi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class PostActivity extends Activity {
public static final String ENCODING = "UTF-8";
private static final String postURL = "http://api.twitter.com/1/statuses/update.json";
//Keys & Secrets
private static final String consumerKey = "r3dmV3mkbacY4CKdtAaNQG";
private static final String consumerSecret = "gomuK2bcO7KPfUyk1PHtx91AAIEQCumvrYxLUwCiHlg";
private static final String accessToken = "700867350-eFsXm3YteXZ2QRZY9Ydq2RDMkk1ZdFcnSMnb8Izr";
private static final String accessTokenSecret = "e9bj6RDFGFBL7kOILJBgfXEEIMZ6uRwqtTggxpnLM";
//OAuth Header Keys
private static final String oauth_consumer_key = "oauth_consumer_key";
private static final String oauth_nonce = "oauth_nonce";
private static final String oauth_signature = "oauth_signature";
private static final String oauth_signature_method = "oauth_signature_method";
private static final String oauth_timestamp = "oauth_timestamp";
private static final String oauth_token = "oauth_token";
private static final String oauth_version = "oauth_version";
private static final String status = "status";
private EditText tweetEditor;
private Button postButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.post_main);
postButton = (Button)findViewById(R.id.postButton);
tweetEditor = (EditText)findViewById(R.id.tweetEditor);
tweetEditor.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(tweetEditor.getText().length() == 0) {
postButton.setEnabled(false);
}
else {
postButton.setEnabled(true);
}
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) { }
public void afterTextChanged(Editable s) { }
});
}
private void alertUser(String str) {
new AlertDialog.Builder(this)
.setMessage(str)
.setNeutralButton("OK", null)
.show();
}
public void post(View button) {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(postURL);
try {
String message = tweetEditor.getText().toString();
addAuthorizationHeader(message, post);
post.setEntity(new StringEntity(percentEncode(status) + "=" + percentEncode(message)));
HttpResponse response = client.execute(post);
parsePostResponse(response);
}
catch(Exception e) {
Log.e(PostActivity.class.getName(), e.getMessage());
alertUser("Error: " + e.getMessage());
}
}
private void addAuthorizationHeader(String tweet, HttpRequestBase request) throws Exception {
request.setHeader("Content-Type", "application/x-www-form-urlencoded");
String nonce = base64Encode((UUID.randomUUID().toString().replaceAll("-", "").getBytes()));
String signatureMethod = "HMAC-SHA1";
String timeStamp = String.valueOf(new Date().getTime() / 1000);
String version = "1.0";
//Generating the Parameter String:
//Add request parameters and status message alphabetically (DO NOT ADD SIGNATURE)
//Encode keys and values while adding
//Insert = character between each key and its value
//Add an ampersand (&) to the end if there are more parameters
String parameterString =
percentEncode(oauth_consumer_key) + "=" + percentEncode(consumerKey) + "&" +
percentEncode(oauth_nonce) + "=" + percentEncode(nonce) + "&" +
percentEncode(oauth_signature_method) + "=" + percentEncode(signatureMethod) + "&" +
percentEncode(oauth_timestamp) + "=" + percentEncode(timeStamp) + "&" +
percentEncode(oauth_token) + "=" + percentEncode(accessToken) + "&" +
percentEncode(oauth_version) + "=" + percentEncode(version) + "&" +
percentEncode(status) + "=" + percentEncode(tweet);
//Generate the SignatureBaseString
String signatureBaseString = "POST&" + percentEncode(postURL) + "&" + percentEncode(parameterString);
//Generate the SigningKey
String signingKey = percentEncode(consumerSecret) + "&" + percentEncode(accessTokenSecret);
//Generate HMAC-MD5 signature
String signature = generateHmacSHA1(signingKey, signatureBaseString);
//Build the HTTP Header
String oauthHeader =
"OAuth " +
percentEncode(oauth_consumer_key) + "=\"" + percentEncode(consumerKey) + "\", " +
percentEncode(oauth_nonce) + "=\"" + percentEncode(nonce) + "\", " +
percentEncode(oauth_signature) + "=\"" + percentEncode(signature) + "\", " +
percentEncode(oauth_signature_method) + "=\"" + percentEncode(signatureMethod) + "\", " +
percentEncode(oauth_timestamp) + "=\"" + percentEncode(timeStamp) + "\", " +
percentEncode(oauth_token) + "=\"" + percentEncode(accessToken) + "\", " +
percentEncode(oauth_version) + "=\"" + percentEncode(version) + "\"";
request.addHeader("Authorization", oauthHeader);
}
private String generateHmacSHA1(String key, String value) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(ENCODING), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(keySpec);
byte[] result = mac.doFinal(value.getBytes(ENCODING));
return base64Encode(result);
}
private void parsePostResponse(HttpResponse response) throws IllegalStateException, IOException, JSONException {
StringBuilder sb = getResponseBody(response);
if(response.getStatusLine().getStatusCode() == 200) {
alertUser("Tweet Successful!\nID: " + new JSONObject(sb.toString()).get("id"));
}
//Not OK
else {
Log.e(PostActivity.class.getName(), "Response Code: " + response.getStatusLine().getStatusCode() + "\nResponse: " + sb.toString());
alertUser("Error Code: " + response.getStatusLine().getStatusCode() + "\n" + new JSONObject(sb.toString()).getString("error"));
}
}
private static StringBuilder getResponseBody(HttpResponse response) throws IllegalStateException, IOException {
InputStream is = response.getEntity().getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
return sb;
}
private String percentEncode(String s) throws UnsupportedEncodingException {
//This could be done faster with more hand-crafted code.
return URLEncoder.encode(s, ENCODING)
// OAuth encodes some characters differently:
.replace("+", "%20").replace("*", "%2A")
.replace("%7E", "~");
}
private static String base64Encode(byte[] array) {
return Base64.encodeToString(array, Base64.NO_WRAP);
}
}
A simple layout file is all we need to gather the tweet input:
post_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal" >
<EditText
android:id="@+id/tweetEditor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="65"
android:inputType="textMultiLine"
android:maxLength="140"
android:padding="10dp"
android:textAppearance="@android:style/TextAppearance.Small" />
<Button
android:id="@+id/postButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="35"
android:drawablePadding="5dp"
android:drawableRight="@drawable/twitter_logo"
android:enabled="false"
android:onClick="post"
android:text="Tweet"
android:textAppearance="@android:style/TextAppearance.Small" />
</LinearLayout>
And this is how it looks:
![]() |
| Figure 2: Using the Twitter Rest API for POST statuses/update |
Simple Twitter Client
Now that we know how to do GET/POST requests using the Rest API, we can combine the codes from the previous projects and build ourselves a simple twitter client with a little bit of extra tweaking. The first example retrieved the user's own tweets (by using user_timeline method) but since we now want to see tweets from others as well (just like twitter.com), we will be using GET statuses/home_timeline method instead, which does require authorization. This example executes POST statuses/update requests on the UI thread, but uses an AsyncTask for GET statuses/home_timeline executions since it might take a fair bit of time to retrieve the last 20 tweets and parse them. Also, a Timer updates the tweets every 10 seconds when the screen is active.SimpleTwitterClient.java
package com.gtware.android.twitter.restapi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
public class SimpleTwitterClient extends ListActivity {
public static final String ENCODING = "UTF-8";
private static final String postURL = "http://api.twitter.com/1/statuses/update.json";
private static final String getHomeTimelineURL = "http://api.twitter.com/1/statuses/home_timeline.json";
//Consumer & Access Keys & Secrets
private static final String consumerKey = "r3dMV3mLbacY4CKdtAaNQ";
private static final String consumerSecret = "FomuK2bcO7KPfUyk1PHtx91AAIEQCumvrYxLUwCiHlg";
private static final String accessToken = "700867251-eFsXm3YteXZ2QRZY9Ydq2RDMkk1ZdFcnSMnb8Izr";
private static final String accessTokenSecret = "e9Cj6RDFGMBL7kOILJBgfXEEIMZ6uRwqtTggxpnLM";
//OAuth Header Keys
private static final String oauth_consumer_key = "oauth_consumer_key";
private static final String oauth_nonce = "oauth_nonce";
private static final String oauth_signature = "oauth_signature";
private static final String oauth_signature_method = "oauth_signature_method";
private static final String oauth_timestamp = "oauth_timestamp";
private static final String oauth_token = "oauth_token";
private static final String oauth_version = "oauth_version";
private static final String status = "status";
private EditText tweetEditor;
private Button postButton;
private TextView label;
private ListView list;
private ArrayAdapter<Tweet> adapter;
private Timer timer;
private class Tweet {
String name;
String screenName;
String text;
String date;
private Tweet(String name, String screenName, String text, String date) {
this.name = name;
this.screenName = screenName;
this.text = text;
this.date = date;
}
@Override
public String toString() {
return name + " - @" + screenName + "\n" + text + "\n" + date;
}
}
private ArrayList<Tweet> tweets = new ArrayList<Tweet>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_twitter_client);
postButton = (Button)findViewById(R.id.postButton);
tweetEditor = (EditText)findViewById(R.id.tweetEditor);
tweetEditor.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(tweetEditor.getText().length() == 0) {
postButton.setEnabled(false);
}
else {
postButton.setEnabled(true);
}
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) { }
public void afterTextChanged(Editable s) { }
});
label = (TextView)findViewById(R.id.label);
list = getListView();
adapter = new ArrayAdapter<Tweet>(this, android.R.layout.simple_list_item_1, tweets);
list.setAdapter(adapter);
}
@Override
protected void onResume() {
super.onResume();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
updateTweets(null);
}
});
}
}, 0, 10000);
}
@Override
protected void onPause() {
super.onPause();
if(timer != null) {
timer.cancel();
}
}
public void updateTweets(View button) {
getHomeTimeline();
}
private void alertUser(String str) {
new AlertDialog.Builder(this)
.setMessage(str)
.setNeutralButton("OK", null)
.show();
}
public void post(View button) {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(postURL);
try {
String message = tweetEditor.getText().toString();
addAuthorizationHeader(message, "POST", postURL, post);
post.setEntity(new StringEntity(percentEncode(status) + "=" + percentEncode(message)));
HttpResponse response = client.execute(post);
parsePostResponse(response);
}
catch(Exception e) {
Log.e(SimpleTwitterClient.class.getName(), e.getMessage());
alertUser("Error: " + e.getMessage());
}
}
private void getHomeTimeline() {
class TwitterTask extends AsyncTask<Void, Void, HttpResponse> {
@Override
protected HttpResponse doInBackground(Void... params) {
try {
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(getHomeTimelineURL);
addAuthorizationHeader(null, "GET", getHomeTimelineURL, get);
return client.execute(get);
}
catch(final Exception e) {
Log.e(GetActivity.class.getName(), e.getMessage());
label.post(new Runnable() {
public void run() {
label.setText("Error: " + e.getMessage());
}
});
return null;
}
}
@Override
protected void onPostExecute(HttpResponse response) {
super.onPostExecute(response);
if(response != null) {
try {
parseGetHomeTimelineResponse(response);
adapter.notifyDataSetChanged();
}
catch(Exception e) {
Log.e(GetActivity.class.getName(), e.getMessage());
label.setText("Error: " + e.getMessage());
}
}
}
}
new TwitterTask().execute();
}
private void addAuthorizationHeader(String tweet, String method, String url, HttpRequestBase request) throws Exception {
if(!method.equals("GET") && !method.equals("POST")) {
throw new IllegalArgumentException();
}
request.setHeader("Content-Type", "application/x-www-form-urlencoded");
String nonce = base64Encode((UUID.randomUUID().toString().replaceAll("-", "").getBytes()));
String signatureMethod = "HMAC-SHA1";
String timeStamp = String.valueOf(new Date().getTime() / 1000);
String version = "1.0";
//Generating the Parameter String:
//Add request parameters and status message alphabetically (DO NOT ADD SIGNATURE)
//Encode keys and values while adding
//Insert = character between each key and its value
//Add an ampersand (&) to the end if there are more parameters
String parameterString =
percentEncode(oauth_consumer_key) + "=" + percentEncode(consumerKey) + "&" +
percentEncode(oauth_nonce) + "=" + percentEncode(nonce) + "&" +
percentEncode(oauth_signature_method) + "=" + percentEncode(signatureMethod) + "&" +
percentEncode(oauth_timestamp) + "=" + percentEncode(timeStamp) + "&" +
percentEncode(oauth_token) + "=" + percentEncode(accessToken) + "&" +
percentEncode(oauth_version) + "=" + percentEncode(version);
if(tweet != null) {
parameterString += "&" + percentEncode(status) + "=" + percentEncode(tweet);;
}
//Generate the SignatureBaseString
String signatureBaseString = method + "&" + percentEncode(url) + "&" + percentEncode(parameterString);
//Generate the SigningKey
String signingKey = percentEncode(consumerSecret) + "&" + percentEncode(accessTokenSecret);
//Generate HMAC-MD5 signature
String signature = generateHmacSHA1(signingKey, signatureBaseString);
//Build the HTTP Header
String oauthHeader =
"OAuth " +
percentEncode(oauth_consumer_key) + "=\"" + percentEncode(consumerKey) + "\", " +
percentEncode(oauth_nonce) + "=\"" + percentEncode(nonce) + "\", " +
percentEncode(oauth_signature) + "=\"" + percentEncode(signature) + "\", " +
percentEncode(oauth_signature_method) + "=\"" + percentEncode(signatureMethod) + "\", " +
percentEncode(oauth_timestamp) + "=\"" + percentEncode(timeStamp) + "\", " +
percentEncode(oauth_token) + "=\"" + percentEncode(accessToken) + "\", " +
percentEncode(oauth_version) + "=\"" + percentEncode(version) + "\"";
request.addHeader("Authorization", oauthHeader);
}
private String generateHmacSHA1(String key, String value) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(ENCODING), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(keySpec);
byte[] result = mac.doFinal(value.getBytes(ENCODING));
return base64Encode(result);
}
private void parsePostResponse(HttpResponse response) throws IllegalStateException, IOException, JSONException {
StringBuilder sb = getResponseBody(response);
if(response.getStatusLine().getStatusCode() == 200) {
getHomeTimeline();
//alertUser("Tweet Successful!\nID: " + new JSONObject(sb.toString()).get("id"));
}
//Not OK
else {
Log.e(SimpleTwitterClient.class.getName(), "Response Code: " + response.getStatusLine().getStatusCode() + "\nResponse: " + sb.toString());
alertUser("Error Code: " + response.getStatusLine().getStatusCode() + "\n" + new JSONObject(sb.toString()).getString("error"));
}
}
private void parseGetHomeTimelineResponse(HttpResponse response) throws IllegalStateException, IOException, JSONException {
StringBuilder sb = getResponseBody(response);
if(response.getStatusLine().getStatusCode() == 200) {
tweets.clear();
JSONArray jsTweets = new JSONArray(sb.toString());
if(jsTweets.length() == 0) {
label.setText("No tweets");
}
else {
label.setText("Last " + jsTweets.length() + " tweet(s):");
for(int i = 0; i < jsTweets.length(); i++) {
JSONObject jsTweet = jsTweets.getJSONObject(i);
JSONObject jsUser = jsTweet.getJSONObject("user");
Tweet tweet = new Tweet(jsUser.getString("name"), jsUser.getString("screen_name"), jsTweet.getString("text"), jsTweet.getString("created_at"));
tweets.add(tweet);
}
}
}
//Not OK
else {
Log.e(SimpleTwitterClient.class.getName(), "Response Code: " + response.getStatusLine().getStatusCode() + "\nResponse: " + sb.toString());
alertUser("Error Code: " + response.getStatusLine().getStatusCode() + "\n" + new JSONObject(sb.toString()).getString("error"));
}
}
private static StringBuilder getResponseBody(HttpResponse response) throws IllegalStateException, IOException {
InputStream is = response.getEntity().getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
return sb;
}
private String percentEncode(String s) throws UnsupportedEncodingException {
//This could be done faster with more hand-crafted code.
return URLEncoder.encode(s, ENCODING)
// OAuth encodes some characters differently:
.replace("+", "%20").replace("*", "%2A")
.replace("%7E", "~");
}
private static String base64Encode(byte[] array) {
return Base64.encodeToString(array, Base64.NO_WRAP);
}
}
simple_twitter_client.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/tweetEditor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="65"
android:inputType="textMultiLine"
android:maxLength="140"
android:padding="10dp"
android:textAppearance="@android:style/TextAppearance.Small" />
<Button
android:id="@+id/postButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="35"
android:drawablePadding="5dp"
android:drawableRight="@drawable/twitter_logo"
android:enabled="false"
android:onClick="post"
android:text="Tweet"
android:textAppearance="@android:style/TextAppearance.Small" />
</LinearLayout>
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingTop="10dp" />
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#0041c5"
android:dividerHeight="2dp"/>
</LinearLayout>
Output:
![]() |
| Figure 3: A simple Twitter client using the Rest API |
Reading tweets can also be done using the Streaming API, but that would be another blog. There's definitely a lot of work to do to make this client usable in real life such as getting the links to work and better tweet parsing, but this example provides a good knowledge and code base to use the Rest API in your applications.
Further Reading:
Twitter Rest API
Twitter Rest API GET statuses/user_timeline
Twitter Rest API POST statuses/update
Twitter Rest API GET statuses/home_timeline
Authorizing a request
Creating a signature
Percent encoding parameters
Twitter Web Intents
Working with Timelines
Tweets
Twitter Apps
The Streaming APIs
twitter4j
Tweeting from Android Apps using twitter4j
Apache HTTP Client




This was an amazing post that helped me a ton. Thanks!
ReplyDeleteThanks for the info! :D
ReplyDeleteThis comment has been removed by the author.
ReplyDelete