Monday 17 October 2016

java - Oauth 2.0 Example Unable to start activity ComponentInfo - NullPointerException

I found this excellent example of Application-Only Authorization (OAuth 2.0) and OAuth 1.0a. The problem is the examples are both displayed in the same java file and I'm really interested in testing the capabilities of the OAuth 2.0 read-only authorization. When I try to comment out and disable parts of the 0Auth 1.0a, it blows up. I'm also trying to minimize the amount of buttons used down to two (1a & 1b) or even none, but I keep running into errors. I'll post the problem code, but if you want to use the full example code as is, it can be found here: http://ttlnews.blogspot.com/2013/07/integrating-twitter-11-api-in-android.html?m=1



Error Log:



11-18 22:32:22.355: E/AndroidRuntime(32334): FATAL EXCEPTION: main
11-18 22:32:22.355: E/AndroidRuntime(32334): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.twitterclientoauth/com.example.twitterclientoauth.OAuthTwitterClient}: java.lang.NullPointerException
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2246)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2296)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.app.ActivityThread.access$700(ActivityThread.java:151)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1281)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.os.Handler.dispatchMessage(Handler.java:99)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.os.Looper.loop(Looper.java:137)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.app.ActivityThread.main(ActivityThread.java:5293)
11-18 22:32:22.355: E/AndroidRuntime(32334): at java.lang.reflect.Method.invokeNative(Native Method)
11-18 22:32:22.355: E/AndroidRuntime(32334): at java.lang.reflect.Method.invoke(Method.java:511)
11-18 22:32:22.355: E/AndroidRuntime(32334): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
11-18 22:32:22.355: E/AndroidRuntime(32334): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
11-18 22:32:22.355: E/AndroidRuntime(32334): at dalvik.system.NativeStart.main(Native Method)
11-18 22:32:22.355: E/AndroidRuntime(32334): Caused by: java.lang.NullPointerException
11-18 22:32:22.355: E/AndroidRuntime(32334): at com.example.twitterclientoauth.OAuthTwitterClient.onCreate(OAuthTwitterClient.java:96)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.app.Activity.performCreate(Activity.java:5250)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1097)
11-18 22:32:22.355: E/AndroidRuntime(32334): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2210)
11-18 22:32:22.355: E/AndroidRuntime(32334): ... 11 more


OAuthTwitterClient.java



package com.example.twitterclientoauth;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.HttpsURLConnection;

import oauth.signpost.OAuth;
import oauth.signpost.OAuthProvider;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthProvider;
import oauth.signpost.exception.OAuthNotAuthorizedException;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.HTTP;
import org.json.simple.JSONValue;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
* Simple quite ugly looking UI to show usage of the new Twitter API v1.1.
* Includes tweeting and app-only authentication.
*
*/
public class OAuthTwitterClient extends Activity {


private static final String USER_AGENT = "OAuthTwitterClient app-only auth";
/*private EditText tweetText = null;*/
private TextView buttonStatus = null;
/*private Button loginButton = null;*/
private Button bearerTokenButton = null;
/*private Button tweetButton = null;*/
private Button getTweetButton = null;
private TextView result = null;

private String twitterUsername= "twitter"; // The username to get the tweet for
private String tweet;

private LinearLayout wrappingLayout = null;

private static final String CALLBACK_URL = "Insert_Callback_URL"; // TO CHANGE!!
private static final String CONSUMER_KEY = "Consumer_Key"; // TO CHANGE!!
private static final String CONSUMER_SECRET = "Consumer_Secret"; // TO CHANGE!!

private OAuthProvider provider = null;
private CommonsHttpOAuthConsumer consumer = null;
//private HttpClient client = null;

private String bearerToken;


@Override
public void onCreate(Bundle savedInstance) {

Log.d(getClass().getSimpleName(), "Entering");

super.onCreate(savedInstance);

setContentView(R.layout.activity_main);
Log.d(getClass().getSimpleName(), "After setting content");

//wrappingLayout = (LinearLayout) findViewById(R.id.wrappingLayout);
//tweetText = (EditText) wrappingLayout.findViewById(R.id.tweetText);

**buttonStatus = (TextView) wrappingLayout.findViewById(R.id.status);**

//loginButton = (Button) wrappingLayout.findViewById(R.id.loginButton);
//loginButton.setOnClickListener(loginOnClickListener);

//tweetButton = (Button) wrappingLayout.findViewById(R.id.tweetButton);
//tweetButton.setOnClickListener(tweetOnClickListener);

bearerTokenButton = (Button) wrappingLayout.findViewById(R.id.getBearerTokenButton);
bearerTokenButton.setOnClickListener(getBearerTokenOnClickListener);

getTweetButton = (Button) wrappingLayout.findViewById(R.id.getTweetButton);
getTweetButton.setOnClickListener(getTweetOnClickListener);

result = (TextView) wrappingLayout.findViewById(R.id.result);

// Setup oauth stuff that only needs to happen once.
consumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);

provider = new CommonsHttpOAuthProvider("https://api.twitter.com/oauth/request_token", "https://api.twitter.com/oauth/access_token",
"https://api.twitter.com/oauth/authorize");

//client = new DefaultHttpClient();
// If you get this error: "No trusted server certificate", try replacing the above statement with next statement to load own keystore with correct Twitter certificates:
// client = new CrazyBobHttpClient(getApplicationContext());

}


/**
* Listens to click on login button
*/
/* private OnClickListener loginOnClickListener = new OnClickListener() {

public void onClick(View v) {

Log.d(getClass().getSimpleName(), "Login button pressed");
buttonStatus.setText("Loginbutton clicked");
result.setText("");

String authUrl = null;
try {
authUrl = provider.retrieveRequestToken(consumer, CALLBACK_URL);
} catch (Exception ex) {
Log.e(getClass().getSimpleName(), "Unable to retrieveRequestToken, exception ", ex );
throw new RuntimeException(ex);
}

// Note the singleInstance setting in the manifest.xml
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));

}
};
*/

/**
* Listens to click on the tweet button
*/
/* private OnClickListener tweetOnClickListener = new OnClickListener() {

public void onClick(View v) {

Log.d(getClass().getSimpleName(), "Tweet button pressed");
buttonStatus.setText("Tweetbutton clicked");
result.setText("");

// create a request that requires authentication
HttpPost post = new HttpPost("https://api.twitter.com/1.1/statuses/update.json");

// Set up the tweet contents
final List nvps = new ArrayList();


try {

post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
// set this to avoid 417 error (Expectation Failed)
post.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
// sign the request
consumer.sign(post);

// send the request
final HttpResponse response = client.execute(post);
// response status should be 200 OK
int statusCode = response.getStatusLine().getStatusCode();
final String reason = response.getStatusLine().getReasonPhrase();

// Bit weird order, statusCode *can* be checked earlier, is there a special reason?
if (statusCode != 200) {
Log.e(getClass().getSimpleName(), "TwitterConnector failed, statusCode not 200 but " + statusCode +", reason = " + reason);
throw new OAuthNotAuthorizedException();
}

// release connection
response.getEntity().consumeContent();

result.setText("Succssfully tweeted");

} catch (Exception ex) {
Log.e(getClass().getSimpleName(), "Unable to post tweet update, exception = ", ex);
result.setText(ex.getMessage());
}
}

};*/

/**
* Listens to click on the get-tweet button
*/
private OnClickListener getTweetOnClickListener = new OnClickListener() {

public void onClick (View v) {

Log.d(getClass().getSimpleName(), "getTweet button pressed");
buttonStatus.setText("getTweet button clicked");
result.setText("");
tweet = "";

// Obtain a bearer token. Note here it's done a bit cleaner, not doing heavy processing in the UI thread.
// Code heavily based upon: http://www.dreamincode.net/forums/blog/114/entry-4459-demo-of-twitter-application-only-oauth-authentication-using-java/
try {

tweet = fetchTimelineTweet("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" + twitterUsername + "&count=2", bearerToken);

result.setText("TWEET from user " + twitterUsername + " = " + tweet);
} catch (Exception ex) {
Log.e(getClass().getSimpleName(), "Unable to get bearer token, exception = ", ex);
//result.setText(ex.getMessage());
}

}
};


/**
* Listens to click on the get-tweet button
*/
private OnClickListener getBearerTokenOnClickListener = new OnClickListener() {

public void onClick(View v) {

Log.d(getClass().getSimpleName(), "getBearerToken button pressed");
buttonStatus.setText("getBearerToken button clicked");
result.setText("");
tweet = "";

// Obtain a bearer token. Note here it's done a bit cleaner, not doing heavy processing in the UI thread.
// Code heavily based upon: http://www.dreamincode.net/forums/blog/114/entry-4459-demo-of-twitter-application-only-oauth-authentication-using-java/
try {
bearerToken = requestBearerToken("https://api.twitter.com/oauth2/token");
Log.d(getClass().getSimpleName(), "bearerToken = " + bearerToken);
if (TextUtils.isEmpty(bearerToken)) {
result.setText("Unsuccessfully retrieved bearer token, empty!");
} else {
result.setText("Successfully retrieved bearer token");
}
} catch (Exception ex) {
Log.e(getClass().getSimpleName(), "Unable to get bearer token, exception = ", ex);
result.setText(ex.getMessage());
}

}
};





/**
* Encodes the consumer key and secret to create the basic authorization key
*/
private String encodeKeys(String consumerKey, String consumerSecret) {
try {
String encodedConsumerKey = URLEncoder.encode(consumerKey, "UTF-8");
String encodedConsumerSecret = URLEncoder.encode(consumerSecret, "UTF-8");

String fullKey = encodedConsumerKey + ":" + encodedConsumerSecret;
byte[] encodedBytes = Base64.encodeBase64(fullKey.getBytes());

return new String(encodedBytes);
}
catch (UnsupportedEncodingException e) {
return new String();
}
}


/**
* Workaround to fix -1 responseCode on a second call via HttpsUrlConnection.
* See: http://stackoverflow.com/questions/1440957/httpurlconnection-getresponsecode-returns-1-on-second-invocation
*/
@SuppressWarnings("deprecation")
private void disableConnectionReuseIfNecessary() {
// HTTP connection reuse which was buggy pre-froyo
// When mindSdk >= 4 use: Build.VERSION.SDK_INT
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.JELLY_BEAN) {
System.setProperty("http.keepAlive", "false");
}
}

/*
* Constructs the request for requesting a bearer token and returns that token as a string
*/
private String requestBearerToken(String endPointUrl) throws IOException {

Log.d(getClass().getName(), "requestBearerToken(): entering");

HttpsURLConnection connection = null;
String encodedCredentials = encodeKeys(CONSUMER_KEY,CONSUMER_SECRET);

try {
URL url = new URL(endPointUrl);
connection = (HttpsURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Host", "api.twitter.com");
connection.setRequestProperty("User-Agent", USER_AGENT);
connection.setRequestProperty("Authorization", "Basic " + encodedCredentials);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
connection.setRequestProperty("Content-Length", "29");
connection.setUseCaches(false);

disableConnectionReuseIfNecessary();

writeRequest(connection, "grant_type=client_credentials");

// Parse the JSON response into a JSON mapped object to fetch fields from.
Object parse = JSONValue.parse(readResponse(connection));
org.json.simple.JSONObject obj = (org.json.simple.JSONObject)parse;

if (obj != null) {
String tokenType = (String)obj.get("token_type");
String token = (String)obj.get("access_token");

return ((tokenType.equals("bearer")) && (token != null)) ? token : "";
}
return ""; // Bit ugly...

} catch (MalformedURLException e) {
throw new IOException("Invalid endpoint URL specified." + e.getMessage());
} finally {
if (connection != null) {
connection.disconnect();
}
}
}




/**
* Fetches the first tweet from a given user's timeline
*/
private String fetchTimelineTweet(String endPointUrl, String bearerToken) throws IOException {

Log.d(getClass().getName(), "fetchTimelineTweet(): entering");
HttpsURLConnection connection = null;

try {
URL url = new URL(endPointUrl);
connection = (HttpsURLConnection) url.openConnection();
// connection.setDoOutput(true); this line causes on (at least from) Android 4.x to return a HTTP 500 and gives a FileNotFoundException on reading the input stream
// See for solution: http://stackoverflow.com/questions/9365829/filenotfoundexception-for-httpurlconnection-in-ice-cream-sandwich
// Similar: http://stackoverflow.com/questions/11810447/httpurlconnection-worked-fine-in-android-2-x-but-not-in-4-1-no-authentication-c
connection.setDoInput(true);
connection.setRequestMethod("GET");
connection.setRequestProperty("Host", "api.twitter.com");
connection.setRequestProperty("User-Agent", USER_AGENT);
connection.setRequestProperty("Authorization", "Bearer " + bearerToken);
connection.setUseCaches(false);

disableConnectionReuseIfNecessary();

// Parse the JSON response into a JSON mapped object to fetch fields from.
org.json.simple.JSONArray obj = (org.json.simple.JSONArray)JSONValue.parse(readResponse(connection));

if (obj != null) {
String tweet = ((org.json.simple.JSONObject)obj.get(0)).get("text").toString();

return (tweet != null) ? tweet : "";
}
return ""; // Bit ugly...
}
catch (MalformedURLException e) {
throw new IOException("Invalid endpoint URL specified." + e.getMessage());
} finally {
if (connection != null) {
connection.disconnect();
}
}
}



/**
* Writes a request to a connection
*/
private static boolean writeRequest(HttpsURLConnection connection, String textBody) {

try {
BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
wr.write(textBody);
wr.flush();
wr.close();

return true;
} catch (IOException e) {
// TODO: needs better error handling
return false;
}
}


/**
* Reads a response for a given connection and returns it as a string.
*/
private static String readResponse(HttpsURLConnection connection) {

try {
StringBuilder str = new StringBuilder();

int responseCode = connection.getResponseCode();
Log.d(OAuthTwitterClient.class.getName(), "readResponse(): Response code = " + responseCode);
if (responseCode != HttpStatus.SC_OK) {

if (responseCode != -1) {
InputStream errorStream = connection.getErrorStream();
InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
BufferedReader br = new BufferedReader(inputStreamReader);
String line = "";
while((line = br.readLine()) != null) {
str.append(line + System.getProperty("line.separator"));
}
} else {
String errorMsg = "Response code = -1, so can't get error stream";
Log.e(OAuthTwitterClient.class.getSimpleName(), errorMsg);
str.append(errorMsg);
}
Log.e(OAuthTwitterClient.class.getSimpleName(), "Error stream output = " + str.toString());
return str.toString();
}
InputStream inputStream = connection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(inputStreamReader);
String line = "";
while((line = br.readLine()) != null) {
str.append(line + System.getProperty("line.separator"));
}

Log.d(OAuthTwitterClient.class.getSimpleName(), "Returning as response string: " + str.toString());

return str.toString();

} catch (IOException e) {

// TODO: better error handling
e.printStackTrace();
throw new IllegalArgumentException(e);
}
}





/**
* Invoked: when browser calls that callback URL then your app is brought forward and activity’s onResume() method is called
* See: http://dev.bostone.us/2009/07/16/android-oauth-twitter-updates/#awp::2009/07/16/android-oauth-twitter-updates/
*
*/
@Override
protected void onResume() {

Log.d(getClass().getSimpleName(), "onResume entered");

Uri uri = this.getIntent().getData();
if (uri != null && uri.toString().startsWith(CALLBACK_URL)) {

Log.d(getClass().getSimpleName(), "Yup it's talking to us! callback_url is ok.");
String verifier = uri.getQueryParameter(OAuth.OAUTH_VERIFIER);
Log.d(getClass().getSimpleName(), "Retrieved query param verifier = " + verifier );
// this will populate token and token_secret in consumer
try{
provider.retrieveAccessToken(consumer, verifier);
} catch (Exception ex) {
Log.e(getClass().getSimpleName(), "Unable to retrieveAccessToken, exception ", ex );
throw new RuntimeException(ex);
}

// At this point you can call consumer#getToken() and consumer#getTokenSecret() to get and save token/secret for subsequent calls. According to Twitter – access token will not expire unless revoked by user
}

super.onResume();
}


/**
* See: http://dev.bostone.us/2009/07/16/android-oauth-twitter-updates/#IDComment29500241
*/
@Override
protected void onNewIntent(Intent intent) {

Log.d(getClass().getSimpleName(), "onNewIntent entered");

Uri uri = intent.getData();
if (uri != null && uri.toString().startsWith(CALLBACK_URL)) {

Log.d(getClass().getSimpleName(), "Yup it's talking to us! callback_url is ok.");
String verifier = uri.getQueryParameter(OAuth.OAUTH_VERIFIER);
Log.d(getClass().getSimpleName(), "Retrieved query param verifier = " + verifier );
// this will populate token and token_secret in consumer
try{
provider.retrieveAccessToken(consumer, verifier);
} catch (Exception ex) {
Log.e(getClass().getSimpleName(), "Unable to retrieveAccessToken, exception ", ex );
throw new RuntimeException(ex);
}

// At this point you can call consumer#getToken() and consumer#getTokenSecret() to get and save token/secret for subsequent calls. According to Twitter – access token will not expire unless revoked by user
result.setText("Authorized!!");
}

super.onNewIntent(intent);
}


}


Any help would be greatly appreciated. Also if anyone knows what to swap OnClick with because I'm not looking for the actions to work with a button listener, but automatically when the app runs, I'd be grateful for that information.

No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...