Since I’ve been playing with Google Tasks, I wanted to look at my task from my Android phone. Sadly, all the application I could find were using a lot more permissions than I like to give. Therefore, I decided to look into why all these permissions are needed. This will be a little tutorial on interacting with Google Tasks on Android.
Disclaimer: I have not yet published an application using this, so it is possible that something is missing. I will update this post as soon as I have more information (or remove this disclaimer if nothing more is needed).
Project Setup
There are two basic files that need modification in order to be able to use Google Tasks on Android.
The first files you need to modify is the AndroidManifest.xml file. You will need to add the android.permission.INTERNET by adding the following line:
1 |
<uses-permission android:name="android.permission.INTERNET" /> |
This permission is used to allow your application to connect to the Internet.
The second file that needs modification is the app/build.gradle file. Adding the following lines to this file will add the needed libraries to your project’s dependencies:
1 2 3 4 5 6 7 8 9 |
dependencies { compile 'com.google.android.gms:play-services-auth:11.8.0' compile 'com.google.api-client:google-api-client:1.23.0' compile 'com.google.api-client:google-api-client-android:1.23.0' compile 'com.google.apis:google-api-services-tasks:v1-rev49-1.23.0' // Force JSR305 version for compatibility compile 'com.google.code.findbugs:jsr305:2.0.1' } |
Do note that I had to force the version of com.google.code.findbugs:jsr305 in order to fix a dependency issue.
Google API Setup
Configuring the Google Backend services for Android is a little bit different from doing so for desktop applications. I already did a quick tutorial on doing the configuration for Android here.
Handling the user’s credentials
In order to get the user’s credentials, you will need to ask for his permission. I went the easy route and asked directly for read/write access to Google Tasks, but you can get the list of scopes that can be used here. In order to be able to get the user’s token, you need to add the requestEmail permission.
Basically, the way the permission flow works is:
- Build a sign-in intent (with the desired permissions);
- Ask the system to fulfill the intent and wait for the response;
- Process the response
The code is quite straightforward:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
private static final int RC_SIGN_IN = 9001; private static final String TASKS_SCOPE = "https://www.googleapis.com/auth/tasks"; private void signIn() { // Build the intent GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestScopes(new Scope(TASKS_SCOPE)) .requestEmail() // Needed for getToken (OAuth2 stuff) .build(); GoogleSignInClient googleSignInClient = GoogleSignIn.getClient(this, gso); Intent signInIntent = googleSignInClient.getSignInIntent(); // Ask the system to fulfill the intent startActivityForResult(signInIntent, RC_SIGN_IN); } private void handleSignInResult(@NonNull Task<GoogleSignInAccount> completedTask) { try { GoogleSignInAccount account = completedTask.getResult(ApiException.class); GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2( this.getApplicationContext(), Collections.singleton(TASKS_SCOPE)); credential.setSelectedAccount(account.getAccount()); // We got credentials ! } catch (ApiException e) { Log.e(TAG, "handleSignInResult, error -> " + e.getMessage()); } } |
Initializing the Google Tasks API
In order to initialize the Google Tasks API, you will need 3 things:
- A JSONFactory
I went with an instance of com.google.api.client.json.jackson2.JacksonFactory simply because it was there and worked - A HttpTransport
Depending on the Android version you are targeting, you must use a different class. Using AndroidHttp.newCompatibleTransport() makes this selection transparent. - The user’s credentials
1 2 3 4 5 6 7 |
HttpTransport transport = AndroidHttp.newCompatibleTransport(); JsonFactory jsonFactory = JacksonFactory.getDefaultInstance(); GoogleAccountCredential credential = ... this.mContext.Service = new com.google.api.services.tasks.Tasks.Builder( transport, jsonFactory, credential) .setApplicationName("Just testing things around") .build(); |
Interacting with Google Tasks on Android
Once you got the Tasks Service in your hands, interacting with it is the same as doing so in Java. Since I’ve been using API 23, I can’t use streams like I used in my java example. Therefore, I did two little helpers ( TaskList findTaskList(TaskLists tasklists, String title) and Task findTask(Tasks tasks, String title)).
Creating a task list
1 2 3 |
tasklist = service.tasklists().insert( new TaskList().setTitle("generated") ).execute(); |
Finding a task list
1 2 3 4 5 6 7 8 9 10 11 |
private TaskList findTaskList(TaskLists tasklists, String title) { for (TaskList tasklist : tasklists.getItems()) { if (tasklist.getTitle().equals(title)) { return tasklist; } } return null; } TaskLists tasklists = service.tasklists().list().execute(); TaskList tasklist = findTaskList(tasklists, "generated"); |
Creating a task
1 2 3 4 5 |
TaskLists tasklists = ... Task task = service.tasks().insert( tasklist.getId(), new Task().setTitle("generated") ).execute(); |
Finding a task
1 2 3 4 5 6 7 8 9 10 11 12 |
private Task findTask(Tasks tasks, String title) { for (Task task : tasks.getItems()) { if (task.getTitle().equals(title)) { return task; } } return null; } TaskList tasklist = ... Tasks tasks = service.tasks().list(tasklist.getId()).execute(); Task task = findTask(tasks, "generated"); |
Updating a task
1 2 3 4 5 |
TaskList tasklist = ... Tasks tasks = service.tasks().list(tasklist.getId()).execute(); Task task = findTask(tasks, "generated"); task.setDue(new DateTime(new Date(2020, 2, 3))); task = service.tasks().update(tasklist.getId(), task.getId(), task).execute(); |
Completing a task
1 2 3 4 5 |
TaskList tasklist = ... Tasks tasks = service.tasks().list(tasklist.getId()).execute(); Task task = findTask(tasks, "generated"); task.setStatus("completed"); task = service.tasks().update(tasklist.getId(), task.getId(), task).execute(); |
Handling a large number of return values
Like any good online service, Google Tasks API handles a large number of values using pagination. By default, most APIs will return a maximum of 100 results. You are allowed to change this value, but in order to get good performance, you should not put it bigger.
Basically, the way to handle pagination is: do your normal call, check if you got a page token and if you do you know there are more results waiting. You can then call the API again with the page token in order to get the next result set:
1 2 3 4 5 6 7 8 9 |
TaskList tasklist = ... Tasks tasks = null String token = null; do { tasks = service.tasks().list(tasklist.getId()).setMaxResults(10l).setPageToken(token).execute(); Log.i(TAG, "Found " + tasks.getItems().size() + " tasks"); token = tasks.getNextPageToken(); Thread.sleep(1000); } while (token != null); |