I’ve recently been working on a new project involving an Azure hosted ASP.NET MVC WebApi application (actually a pair of them) and native mobile and web applications. Everything is – of course – built using Oxygene. For the Android mobile app I was looking for a REST API client library and have settled upon Retrofit and thought I would share the experience.
Retrofit is a Java library that generates REST API Clients dynamically. It doesn’t create swathes of code to be compiled into your app, but generates client classes at runtime. The classes produced implement interfaces which both describe the REST API service they access and also then provide the client API for consumption by the client application.
Perhaps the easiest way to explain is with an example.
Creating and Configuring a Retrofit
First of all, we need an instance of Retrofit for use in the application.
Retrofit uses a fluent builder pattern so to get one we new up a builder and use that to configure and ultimately build the Retrofit object itself:
var retrofit := (new Retrofit.Builder) .baseUrl('http://myauthservice.azurewebsites.net') .addConverterFactory(GsonConverterFactory.create(gson)) .client(httpClient) .build;
To configure the Retrofit object we use the builder to specify a base url from which all our API endpoints extend. Since my REST services communicate using JSON, I am also specifying the use of GsonConverter to manage the mapping of response and request objects between JSON and POJO’s (Plain Ol’ Java Objects).
Of course, as we shall see, those POJO‘s in the world of Oxygene are POPO‘s: Plain Ol’ Pascal Objects (it’s OK, the Java world they inhabit – in this case – will never know).
Eventually these POPO’s will be shared between all of the client applications (Android, iOS as well as .NET, macOS and possibly Win32/Linux clients).
GsonConverter is a Retrofit plugin based in turn on the Google Gson library. The GsonConverterFactory takes a Gson configuration which is also initialised using a fluent builder pattern.
Before showing that, I’ll also mention that Retrofit can employ pluggable HTTP client libraries. I am using OkHttpClient, from the same developers as Retrofit itself. This too uses a fluent builder.
So let’s see the complete initialization required for Retrofit:
var gson := (new GsonBuilder) .setLenient .create; var httpClient := (new OkHttpClient.Builder) .connectTimeout(240, TimeUnit.SECONDS) .readTimeout(240, TimeUnit.SECONDS) .build; var retrofit := (new Retrofit.Builder) .baseUrl('http://myauthservice.azurewebsites.net') .addConverterFactory(GsonConverterFactory.create(gson)) .client(httpClient) .build;
The setLenient() configuration on Gson makes sure that deserialization of JSON is not adversely affected by JSON which is not ‘strictly’ compliant, although only certain deviations are accepted.
Leniency is required, for example, if you have REST API methods which return simple JSON values. i.e. uncontained (that is, not part of any object or array) strings, integers etc.
The only configuration required on the OkHttpClient was to increase the timeouts. The timeouts required are pretty extreme at the moment – my fledgling Azure services are currently hosted in the 100% free performance tiers currently and performance is … what you’d expect for nothing.
A REST API ? Well, well, I Do Declare
As mentioned above, Retrofit needs an interface that describes the REST API you wish to access. I’ll show a simple interface providing just one of my API methods, for logging in with OAuth resource owner password flow.
Here’s the interface:
AuthTokenApi = public interface [POST('/api/token')] [Headers('Accept: application/json')] [FormUrlEncoded] method loginWithPassword([Field('grant_type')] grant: String; [Field('username')] username: String; [Field('password')] password: String; [Field('client_id')] client_id: String; [Field('client_secret')] client_secret: String): Call<TokenResponse>; end;
Retrofit reflects on the annotations (like attributes in .NET and syntactically identical in Oxygene) in the interface to determine the implementation required to satisfy that interface. There can of course be multiple methods in the interface. I am using only one here for simplicity.
First are the annotations on the methods of the interface. These identify the HTTP method involved and the URL to be appended to the base URL of the Retrofit which is used to eventually generate the client.
Method annotations can also specify headers to be included in any request and the manner in which any request content is to be supplied. In this case FormUrlEncoded (application/x-www-form-urlencoded).
In the case of an encoded form request, annotations on the method parameters identify the names of the values to be provided in the request body.
Finally, a Retrofit REST client API method returns a generic Call<T> result. The type T indicates the type of the result value. This can be a simple type (string, integer etc) or – as in this case – a class type (i.e. a POJO).
Here’s my POJO class for that result:
TokenResponse = public class public access_token: String; refresh_token: String; token_type: String; expires_in: Integer; username: String; [SerializedName('as:client_id')] client_id: String; [SerializedName('.issued')] issuedUTC: String; [SerializedName('.expires')] expiresUTC: String; end;
Gson mapping of JSON values to members is by name, but if the names involved do not map directly we can use annotation to provide the serialized name for specific members. In this case, this is required for three members since the JSON names involved are not legal member identifiers.
Pulling It All Together: Making the Call
To call the REST API, we use the Retrofit object we configured to create a class instance providing an implementation of our REST API interface.
We also provide implementations of response and failure handlers for an interface corresponding to a CallBack<T> for the expected T return type. With one slight change to the previous initialization code, to employ a member variable to hold the Retrofit we configure in our activity, here’s a very basic login activity:
type LoginActivity = public class(Activity, Callback<TokenResponse>) private _retrofit: Retrofit; public method onCreate(savedInstanceState: Bundle); override; begin inherited; ContentView := R.layout.login; var gson := (new GsonBuilder) .setLenient .create; var httpClient := (new OkHttpClient.Builder) .connectTimeout(240, TimeUnit.SECONDS) .readTimeout(240, TimeUnit.SECONDS) .build; _retrofit := (new Retrofit.Builder) .baseUrl('http://myauthservice.azurewebsites.net') .addConverterFactory(GsonConverterFactory.create(gson)) .client(httpClient) .build; end; method onLoginClick(view: View); method onResponse(request: Call<TokenResponse>; response: Response<TokenResponse>); method onFailure(request: Call<TokenResponse>; error: Throwable); end; implementation method LoginActivity.onLoginClick(view: View); begin var tokenApi: AuthTokenApi := _retrofit.create(typeOf(AuthTokenApi)); var request := tokenApi.loginWithPassword('password', // The grant type 'someusername', 'supersecretpassword', 'org.myorg.myapp', 'myapp.client.secret.goes.here'); request.enqueue(self); end; method LoginActivity.onResponse(request: Call<TokenResponse>; response: Response<TokenResponse>); begin if response.code = 200 then begin // Successfully logged in. We can access the response POJO directly, // via the body of the response. e.g: // // var token := response.body.access_token; end else begin // Login failed - 401 response etc... end; end; method LoginActivity.onFailure(request: Call<TokenResponse>; error: Throwable); begin // Request failed entirely (e.g. network error) end;
For now let’s look at the onLoginClick method which actually makes the request to the REST server.
var tokenApi: AuthTokenApi := _retrofit.create(typeOf(AuthTokenApi)); var request := tokenApi.loginWithPassword('password', // The grant type 'someusername', 'supersecretpassword', 'org.myorg.myapp', 'myapp.client.secret.goes.here'); request.enqueue(self);
Now let’s break things down a little:
To get an object we can use to call the server, we pass the required interface to the Retrofit.create method.
var tokenApi: AuthTokenApi := _retrofit.create(typeOf(AuthTokenApi));
This returns a reference to a class now implementing that interface. With that object we can obtain (not make) a call to a specific method:
var request := tokenApi.loginWithPassword('password', // The grant type 'someusername', 'supersecretpassword', 'org.myorg.myapp', 'myapp.client.secret.goes.here');
To repeat: This does not call the server. This method returns a Call<TokenResponse> object. Once we have that we can use it (once!) to either execute a call (synchronously) or – as in this case – enqueue an async call, with a reference to a context to handle the response.
With an async call, Retrofit takes care of making all network related calls in a background thread. The response (or failure) handling is then performed in the main thread so that the handlers can update the UI if/as required.
And that’s it.
I’ll follow this up with a closer look at the response and failure handling, in particular the failure handler, to demonstrate how to handle response content that may vary from that declared in the interface.