Skip to content
This repository has been archived by the owner on Dec 26, 2023. It is now read-only.

Short tutorial

Christoffer Klang edited this page Sep 1, 2012 · 4 revisions

Tutorial: Finish the current users latest reading

This tutorial will go through the following steps:

  • Setup the wrapper
  • Get a token from an authorize code
  • Get the current user
  • Get the current users readings
  • Update the latest of the current user's readings

Setup the wrapper

Environment env = Environment.Live;

Selects which server to send requests. In 99% of the cases you'll want to use Environment.Live which is the standard Readmill API server.

ReadmillWrapper wrapper = new ReadmillWrapper("my-client-id", "my-client-secret", env);

Creates a wrapper with your client credentials that you got when you registered your app. You can register a new app or manage your current ones here: https://readmill.com/you/apps

Get a token from an authorize code

If you're accessing authenticated endpoints, then you'll need to obtain an access token. This requires a browser for the user to log in to Readmill and authorize your application.

The wrapper can generate a url where you can send the user, but you need to have a way to caputer the authorization code returned from Readmill once the user allows your app. Once you have the code, then the wrapper can exchange that code for a token.

The full Readmill authorization process is described here

A token is associated with a callback uri and the authentication code received is valid only for that callback. So the first step is the inform the wrapper for which callback we want to retrieve a token.

URI myCallback = URI.create("https://my-readmill-hack.com/callback");
wrapper.setRedirectURI(myCallback);

The next step is to get a url of where to send users so that they can authorize our application.

URL authorizationUrl = wrapper.getAuthorizationUrl();

Once we have sent the user to that url, and gotten the response code from the callback url, then we can get the actual token.

String code = codeRetrievedFromTheCallbackAbove; // step is not included in this tutorial
wrapper.obtainToken(code);

Readmill also provides a way to obtain a token that does not expire. This is easier to implement since the refresh token flow is no longer needed – but it is also less secure so use that feature responsibly.

If you want to request a non expiring token you have to set the scope in the same way as the callback uri.

wrapper.setScope("non-expiring");
URL authorizationUrl = wrapper.getAuthorizationUrl();
// ... get the code
wrapper.obtainToken(code);

Get the current user

To get the user for the token, we need to look into constructing requests. The wrapper allows you to construct complex requests using a RequestBuilder interface that constructs a request by chaining multiple calls together.

You could also construct the requests yourself and pass then directly into the wrapper. In this tutorial we will only look at using the RequestBuilder.

The final call for getting the current user is:

JSONObject user = wrapper.get("/me").fetch("user");

This call has two parts. First we start building a request by telling the wrapper which HTTP method we want, and the endpoint given as a string. In this example we want a GET request to /me.

Then we finish off by calling fetch("user"). This does a few things. It ends the request building and sends the request to the server, parses the response as JSON and unwraps the top level object user.

That last bit probably needs a little elaboration. All responses from Readmill are wrapped in a key that determines the type of the object. For example, the /me endpoint above returns a user, and the json response from Readmill then loooks like this:

{ "user": { id: 1, "username": "christoffer", ... } }

The Readmill API is consistent in formatting their responses like this, so to you might find yourself writing a lot of code that looks like this: .fetch().getOptJSONObject("user"). This pattern in fact so common that the wrapper provides a shortcut to unwrap a top level object directly: fetch("user").

Get the current users readings

In order to get the 10 latest readings of the current user, we use the id from the fetched user, and construct a little longer request by chaining a couple of parameters together.

JSONArray latestReadings;
String myReadingsUrl = String.format("/users/%d/readings", me.getInt("id");

latestReadings = wrapper.get(myReadingsUrl).
                   order("created_at").count(10).
                   fetchItems("reading");

As with single objects, the Readmill API wraps collections under a top level key. As this key is always items the wrapper provides a shortcut for unwrapping collections and returning them as a JSONArray: fetchItems(). Calling fetchItems() is equivalent to calling fetch().optJSONArray("items").

However, objects in collections are also wrapped under their type as the top level key. For example, a collection of readings could look something like:

{
 "items": [
   { "reading": { "id": 123, "state": "reading" } },
   { "reading": { "id": 456, "state": "finished" } },
  ]
}

If all the objects are of the same type, we can take yet another shortcut by passing the objects top level key to fetchItems(), like this: fetchItems("reading"). This unwraps the items and each contained reading all in one swoop.

The example above would thus be transformed by calling fetchItems("reading") into:

[
   { "id": 123, "state": "reading" },
   { "id": 456, "state": "finished" },
]

Update the latest of the current user's readings

Now we know enough to do the final step of the tutorial: get the most recent reading from the array and finish it with a closing remark.

JSONObject mostRecentReading = latestReadings.optJSONObject(0);
String mostRecentReadingUrl = String.format("/readings/%d", mostRecentReading.getInt("id"));

latestReading = wrapper.put(mostRecentReadingUrl).      // send a PUT request to the url
  readingState("finished").                             // set the reading state to "finished"
  readingClosingRemark("Amazing book, front to cover"). // set the closing remark
  fetchJSON("reading");                                 // send the request and get the response

Error handling

Left out from this tutorial, to promote simplicity, is any kind of error handling. The return value of the methods used above (fetch(), fetchItems() etc.) all return null if there is an error either with sending the request or parsing the response as JSON. For simple cases this is usually enough, but for more robust implementations there are alternatives that throw errors fetchAndThrow() and fetchItemsAndThrow() respectively.

If you want even more control you can construct requests using the Request object and pass then to one of get(), post(), etc. The return of those methods will be the bare HTTPResponse object, and they all throw IOError when the requests could not be sent.

As an example, here is the final call of the tutorial with a more manual approach:

try {
  JSONObject mostRecentReading = latestReadings.getJSONObject(0);
} catch(JSONException e) {
  throw new RuntimeException("Invalid JSON in response from Readmill");
}

String mostRecentReadingUrl = String.format("/readings/%d", mostRecentReading.getInt("id"));

Request closeReadingRequest = Request.
  to(mostRecentReadingUrl.toString()).
  withParams(
      "reading[state]", "finished",
      "reading[closing_remark]", "Amazing book, front to cover");
try {
  HTTPReponse response = wrapper.put(closeReadingRequest);
  if(response.getStatusLine().getStatusCode() == 200) {
    JSONObject updatedReading = HTTPUtils.getJSON(response);
  } else {
    throw new RuntimeException("Change was not accepted by the server");
  }
} catch(IOException e) {
  throw new RuntimeException("A network error occurred while sending the request");
} catch(JSONException e) {
  throw new RuntimeException("The server gave an unexpected response");
}