Executing our first API call

We defined a Retrofit service with all API calls, but we haven't connected anything to it yet. It is time to use it. We will extend our code to use Retrofit. Each API call can be executed synchronously or asynchronously. We will show you both ways. Do you remember that we set our Retrofit service base URL to the localhost? This means that we will need a local backend instance that will respond to our HTTP requests. Since backend implementation is not the subject of this book, we will leave it up to you to create a simple service responding to this request. You can implement it from any programming language you like, such as Kotlin, Java, Python, and PHP.

If you are impatient and don't want to implement your own application for dealing with HTTP requests, you can override the base URL, Notes, and TODOs paths as shown in the following example and use the backend instance ready for tryout:

        @POST("authenticate") 
        // @POST("user/authenticate") 
        fun login( 
            ... 
        ): Call<JournalerApiToken> 
        @GET("notes") 
        // @GET("entity/note") 
        fun getNotes( 
            ... 
        ): Call<List<Note>> 
        @GET("todos") 
        // @GET("entity/todo") 
       fun getTodos( 
            ... 
       ): Call<List<Todo>> 

Like this, we will target the remote backend instance returning us stub Notes and TODOs. Now open your JournalerBackendService interface and extend it as follows:

    interface JournalerBackendService { 
      companion object { 
        fun obtain(): JournalerBackendService { 
            return BackendServiceRetrofit 
                    .obtain() 
                    .create(JournalerBackendService::class.java) 
        } 
     } 
      ... 
    } 

The method we just added will give us an instance of JournalerBackendService using Retrofit. Through this, we will trigger all our calls. Open the MainService class. Find the synchronize() method. Remember that we put sleep there to simulate communication with the backend. Now, we will replace it with real backend calls:

    /** 
    * Authenticates user synchronously, 
    * then executes async calls for notes and TODOs fetching. 
    * Pay attention on synchronously triggered call via execute() 
      method. 
    * Its asynchronous equivalent is: enqueue(). 
    */ 
    override fun synchronize() { 
        executor.execute { 
            Log.i(tag, "Synchronizing data [ START ]") 
            var headers = BackendServiceHeaderMap.obtain() 
            val service = JournalerBackendService.obtain() 
            val credentials = UserLoginRequest("username", "password") 
            val tokenResponse = service 
                    .login(headers, credentials) 
                    .execute() 
            if (tokenResponse.isSuccessful) { 
                val token = tokenResponse.body() 
                token?.let { 
                    TokenManager.currentToken = token 
                    headers = BackendServiceHeaderMap.obtain(true) 
                    fetchNotes(service, headers) 
                    fetchTodos(service, headers) 
                } 
            } 
            Log.i(tag, "Synchronizing data [ END ]") 
        } 
    } 
 
    /** 
    * Fetches notes asynchronously. 
    * Pay attention on enqueue() method 
    */ 
    private fun fetchNotes( 
            service: JournalerBackendService, headers: Map<String,  
String> ) { service .getNotes(headers) .enqueue( object : Callback<List<Note>> { verride fun onResponse( call: Call<List<Note>>?, response: Response<List<Note>>? ) { response?.let { if (response.isSuccessful) { val notes = response.body() notes?.let { Db.insert(notes) } } } } override fun onFailure(call:
Call<List<Note>>?, t: Throwable?) { Log.e(tag, "We couldn't fetch notes.") } } ) } /** * Fetches TODOs asynchronously. * Pay attention on enqueue() method */ private fun fetchTodos( service: JournalerBackendService, headers: Map<String,
String> ) { service .getTodos(headers) .enqueue( object : Callback<List<Todo>> { override fun onResponse( call: Call<List<Todo>>?, response:
Response<List<Todo>>? ) { response?.let { if (response.isSuccessful) { val todos = response.body() todos?.let { Db.insert(todos) } } } } override fun onFailure(call:
Call<List<Todo>>?, t: Throwable?) { Log.e(tag, "We couldn't fetch notes.") } } ) }

Analyze the code slowly and take your time! There are a lot of things going on! First, we will create instances of headers and the Journaler backend service. Then, we performed the authentication synchronously by triggering the execute() method. We received Response<JournalerApiToken>. The JournalerApiToken instance is wrapped in the Response class instance. After we check if the response is successful, and that we actually received and deserialized JournalerApiToken, we set it to TokenManager. Finally, we trigger asynchronous calls for Notes and TODOs retrieval.

The enqueue() method triggers the async operation, and, as a parameter, accepts the Retrofit Callback concretization. We will do the same that we did with the sync call. We will check if it is successful and if there is data. If everything is ok, we will pass all instances to our database manager for saving.

We only implemented the Notes and TODOs retrieval. For the rest of the API calls, we leave it up to you to do the implementation. It is a great way to learn Retrofit!

Let's build you an application and run it. As an application and its main service starts, the API calls are executed. Filter the Logcat output by OkHttp. Observe the following.

Authentication log lines:

        D/OkHttp: --> POST 
http://static.milosvasic.net/jsons/journaler/authenticate D/OkHttp: Content-Type: application/json; charset=UTF-8 D/OkHttp: Content-Length: 45 D/OkHttp: Accept: */* D/OkHttp: {"password":"password","username":"username"} D/OkHttp: --> END POST (45-byte body)
       D/OkHttp: <-- 200 OK  
http://static.milosvasic.net/jsons/journaler/
authenticate/ (302ms) D/OkHttp: Date: Sat, 23 Sep 2017 15:46:27 GMT D/OkHttp: Server: Apache D/OkHttp: Keep-Alive: timeout=5, max=99 D/OkHttp: Connection: Keep-Alive D/OkHttp: Transfer-Encoding: chunked D/OkHttp: Content-Type: text/html D/OkHttp: { D/OkHttp: "id_token": "stub_token_1234567", D/OkHttp: "expires": 10000 D/OkHttp: } D/OkHttp: <-- END HTTP (58-byte body)

Notes log lines:

        D/OkHttp: --> GET 
http://static.milosvasic.net/jsons/journaler/notes D/OkHttp: Accept: */* D/OkHttp: Authorization: Bearer stub_token_1234567 D/OkHttp: --> END GET
        D/OkHttp: <-- 200 OK 
http://static.milosvasic.net/jsons/journaler/notes/ (95ms) D/OkHttp: Date: Sat, 23 Sep 2017 15:46:28 GMT D/OkHttp: Server: Apache D/OkHttp: Keep-Alive: timeout=5, max=97 D/OkHttp: Connection: Keep-Alive D/OkHttp: Transfer-Encoding: chunked D/OkHttp: Content-Type: text/html D/OkHttp: [ D/OkHttp: { D/OkHttp: "title": "Test note 1", D/OkHttp: "message": "Test message 1", D/OkHttp: "location": { D/OkHttp: "latitude": 10000, D/OkHttp: "longitude": 10000 D/OkHttp: } D/OkHttp: }, D/OkHttp: { D/OkHttp: "title": "Test note 2", D/OkHttp: "message": "Test message 2", D/OkHttp: "location": { D/OkHttp: "latitude": 10000, D/OkHttp: "longitude": 10000 D/OkHttp: } D/OkHttp: }, D/OkHttp: { D/OkHttp: "title": "Test note 3", D/OkHttp: "message": "Test message 3", D/OkHttp: "location": { D/OkHttp: "latitude": 10000, D/OkHttp: "longitude": 10000 D/OkHttp: } D/OkHttp: } D/OkHttp: ] D/OkHttp: <-- END HTTP (434-byte body)

TODOs log lines:

        D/OkHttp: --> GET
http://static.milosvasic.net/jsons/journaler/todos D/OkHttp: Accept: */* D/OkHttp: Authorization: Bearer stub_token_1234567 D/OkHttp: --> END GET
       D/OkHttp: <-- 200 OK
http://static.milosvasic.net/jsons/journaler/todos/ (140ms) D/OkHttp: Date: Sat, 23 Sep 2017 15:46:28 GMT D/OkHttp: Server: Apache D/OkHttp: Keep-Alive: timeout=5, max=99 D/OkHttp: Connection: Keep-Alive D/OkHttp: Transfer-Encoding: chunked D/OkHttp: Content-Type: text/html D/OkHttp: [ D/OkHttp: { D/OkHttp: "title": "Test todo 1", D/OkHttp: "message": "Test message 1", D/OkHttp: "location": { D/OkHttp: "latitude": 10000, D/OkHttp: "longitude": 10000 D/OkHttp: }, D/OkHttp: "scheduledFor": 10000 D/OkHttp: }, D/OkHttp: { D/OkHttp: "title": "Test todo 2", D/OkHttp: "message": "Test message 2", D/OkHttp: "location": { D/OkHttp: "latitude": 10000, D/OkHttp: "longitude": 10000 D/OkHttp: }, D/OkHttp: "scheduledFor": 10000 D/OkHttp: }, D/OkHttp: { D/OkHttp: "title": "Test todo 3", D/OkHttp: "message": "Test message 3", D/OkHttp: "location": { D/OkHttp: "latitude": 10000, D/OkHttp: "longitude": 10000 D/OkHttp: }, D/OkHttp: "scheduledFor": 10000 D/OkHttp: } D/OkHttp: ] D/OkHttp: <-- END HTTP (515-byte body)

Congratulations! You have implemented your first Retrofit service! Now it is time to implement the rest of the calls. Also, do some code refactoring! This is a small homework task for you. Update your service so it can accept login credentials. In our current code, we hardcoded the username and password. Your mission will be to refactor the code and pass the parameterized credential.

Optionally, improve the code so it's no longer possible to execute the same call multiple times at the same moment. We left this as the legacy from our previous work.