How to Deal with Google SpreadSheet API and OAuth2
This is my first blog post please bear with me. I want to try and help others to deal with the god blessed Google APIs. As many of you may already known before finding this post, Google documentation is lacking Android examples. This post is good as of 20130805, I do not recommend using this example after 5 month of mentioned date. I have noticed a lot of post are old and out of date this is why I have forewarned you about the code below. Nonetheless all old post have helped me understand how to get it to work for me. A big THANK YOU to all!On Android Google recommends the use of AccountManager in some pages and in some others it does not. As far as I can tell AccountManager is the best method of dealing with accounts and making request to OAuth2. After you are all setup, you run into issues with Spreadsheetservice and the end point URI. This is the reason I have posted my solution to my problem.
Please download your libraries and import as posted on the code below for this to work. Do not use the Google Plugins to install Drive APIs and Spreadsheets, in fact remove them.
Libraries required, they can all be found google-oauth-java-client, Google OAuth2 API and gdata-java-client:
gdata-spreadsheet-3.0.jar
gdata-spreadsheet-meta-3.0.jar
google-api-client-android-1.15.0-rc.jar
google-api-services-oauth2-v2-rev40-1.15.0-rc-javadoc.jar
google-api-services-oauth2-v2-rev40-1.15.0-rc-sources.jar
google-api-services-oauth2-v2-rev40-1.15.0-rc.jar
google-http-client-1.15.0-rc.jar
google-http-client-android-1.15.0-rc.jar
google-oauth-client-1.15.0-rc.jar
gson-2.1.jar
jackson-core-2.1.3.jar
jackson-core-asl-1.9.11.jar
jsr305-1.3.9.jar
protobuf-java-2.4.1.jar
google-play-services.jar (use instructions below code to install)
gdata-core-1.0.jar
guava-11.0.2.jar
gdata-client-meta-1.0.jar
On your manifest file do not forget
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
There is more instructions and references after the code. Please comment if this is helpful, requires updating or if I just plain need psychological help.
package com.example.dailyexpense;
/**
* Developed with the help of too many to mention, but to all THANKS!
* By: Antonio M Vazquez
* Date last: 20130805
* This program read all spreadsheets available in google drive. This program will add rows to specific worksheets in
* Google drive worksheets.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.*;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import android.os.Bundle;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.widget.EditText;
/*
* Required google-play-services to allow account picker to lunch.
* Additional libraries install by hand. Google Plugins do not play well with each other.
*/
public class MainActivity extends Activity {
GoogleAccountCredential GACredential;
EditText txtDump; //I like to see what I do so created a debugger window on the device
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//created debuger viewer
txtDump = (EditText) findViewById(R.id.etDump);
//Set up the scopes needed by GSSAPI (Giuseppi)
final List<String> SCOPES = new ArrayList<String>();
SCOPES.add("https://spreadsheets.google.com/feeds/");
SCOPES.add("https://docs.google.com/feeds/");
GACredential = GoogleAccountCredential.usingOAuth2(this, SCOPES);
startActivityForResult(GACredential.newChooseAccountIntent(), 1);
}
@Override
protected void onActivityResult(final int requestCode, final int reultCode, final Intent data){
AccountManager am = AccountManager.get(this);
//Received data with user account name after default account picker was used.
//basically email address
String accountname = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
txtDump = (EditText) findViewById(R.id.etDump);
//Set selected account name
GACredential.setSelectedAccountName(accountname);
//Display what I have so far
txtDump.append(accountname+"\n");
txtDump.append("credential.scope - " + GACredential.getScope()+"\n");
//To get auth token it must be done in background process as UI may frezze
//The call back is handle within theis method and forwaded to useSprtexteadsheetAPI
am.getAuthToken(GACredential.getSelectedAccount(), GACredential.getScope(),
null, this, new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
// Callback received information on token request for later to use
try {
Bundle bundleResults;
bundleResults = future.getResult();
//Just testing and displaying bundle and future information
txtDump.append("\n Account Manager Callback \n");
txtDump.append("KEY_ACCOUNT_NAME - " + future.getResult().getString(AccountManager.KEY_ACCOUNT_NAME) +"\n");
String token = future.getResult().getString(AccountManager.KEY_AUTHTOKEN);
txtDump.append("KEY_AUTHTOKEN - " + token +"\n");
txtDump.append("KEY_INTENT - " + future.getResult().getString(AccountManager.KEY_INTENT) +"\n\n");
//Send results in a bundle to the API accessor
useSpreadsheetAPI(bundleResults);
} catch (OperationCanceledException e) {
// TODO Auto-generated catch block
txtDump.append("OperationCanceledException \n\n");
e.printStackTrace();
} catch (AuthenticatorException e) {
// TODO Auto-generated catch block
txtDump.append("AuthenticatorException \n\n");
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
txtDump.append("IOException \n\n");
e.printStackTrace();
}
}
}, null);
}
public void useSpreadsheetAPI(Bundle myBundle) {
txtDump = (EditText) findViewById(R.id.etDump);
//Instantiated here to invalidate token on service error
AccountManager am = AccountManager.get(this);
//create the service with the app name
SpreadsheetService ssservice = new SpreadsheetService("dailyexpense");
String myToken = myBundle.getString(AccountManager.KEY_AUTHTOKEN);
/*
* Assign my token to the service. Accountmanager above is able to get this token to access Giusepi without
* Client ID or key for the registered app. What is interesting is that there is vague refenrences about this.
*/
ssservice.setAuthSubToken(myToken);
//Playing with values to test what is received from the callback future etc.
txtDump.append("\n\n Entered useSpreadsheetAPI \n");//The code below crashes!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
txtDump.append("AUTHTOKEN -" + myToken + "\n");
txtDump.append("KEY_ACCOUNT_TYPE -" + myBundle.getString(AccountManager.KEY_ACCOUNT_TYPE) + "\n");
txtDump.append("KEY_AUTHENTICATOR_TYPES -" + myBundle.getString(AccountManager.KEY_AUTHENTICATOR_TYPES) + "\n");
txtDump.append("KEY_INTENT - " + myBundle.getString(AccountManager.KEY_INTENT) + "\n");
try {
/*
* Painfully attempted to use URL method to create my "URL SPREADSHEET_FEED_URL =
* new URL("https://spreadsheets.google.com/feeds/spreadsheets/private/full");"
* The above does not work for me I get 400 error URI not properly formated
* This FeedURLFactory.getDefault().getSpreadsheetsFeedUrl() gets the valid service alias value
* or URI... God bless google...
*
*/
SpreadsheetFeed feed = ssservice.getFeed(FeedURLFactory.getDefault().getSpreadsheetsFeedUrl(), SpreadsheetFeed.class);
//View what FeedURLFactory.getDefault().getSpreadsheetsFeedUrl() provides
txtDump.append("URL - " + FeedURLFactory.getDefault().getSpreadsheetsFeedUrl() + "\n");
txtDump.append("Feed plain- " + feed.getTitle().getPlainText() + "\n");
txtDump.append("\n Listing all Spreadhssets and Keys \n");
//Creating the list of spreasheets in GDrive
List<SpreadsheetEntry> spreadsheets = feed.getEntries();
//parsing trough the feed entries
for (int i = 0; i < spreadsheets.size(); i++){
SpreadsheetEntry entry = (SpreadsheetEntry) spreadsheets.get(i);
txtDump.append(entry.getTitle().getPlainText() + "\n\t" + entry.getKey() +"\n");
}
//View what information is in Feed
txtDump.append("Feed - " + feed.toString() + "\n");
} catch (IOException e) {
// TODO Auto-generated catch block
txtDump.append("IOExeption \n");
e.printStackTrace();
} catch (ServiceException e) {
// TODO Auto-generated catch block
txtDump.append("ServiceExeption - " + e.getHttpErrorCodeOverride() + "\n");
//If service error is 400 or 401 it resets the token, you must restart app to use new token
am.invalidateAuthToken(myBundle.getString(AccountManager.KEY_ACCOUNT_TYPE), myBundle.getString(AccountManager.KEY_AUTHTOKEN));
e.printStackTrace();
}
}
@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;
}
}
Additional Instructions and References
I have developed the code above on Eclipse Kepler (Fedora 19). If you are using this setup follow links below on how to install and prepare Eclipse of Fedora 19. Setup Android Development Fedora-Eclipse, Eclipse ADT Plugin Additional references. Install Google play services, and Google Plugins for Eclipse ( this did not really helped me since I added all libraries manually, in fact caused more issues). This last plugin makes all google APIs readily available but the libraries have a tendency to be duplicated and cause errors.Make sure you register your app using Google console. Console, you do not need to enable any services in console.
Use Generate Key fingerprint SHA1 instruction to create your SHA1 ONLY. If this is your first project on Eclipse you may not have a ~/.android/debug.keystore signing certificate fingerprint (SHA1) because you have not run debugger on Eclipse. Create the easy hello world example and run in debug mode to have the debug.keystore created and then run keytool as per Generate Key fingerprint SHA1.