Preparing the Service
To start, open MovieDiaryApiService.kt and replace the code inside with the following:
@POST("user/register")
fun registerUser(@Body body: RequestBody): Call<ResponseBody>
@POST("user/login")
fun loginUser(@Body body: RequestBody): Call<ResponseBody>
@GET("user")
fun getProfile(): Call<ResponseBody>
@GET("sampleMovies")
fun getMovies(): Call<ResponseBody>
@POST("movies")
fun postReview(@Body body: RequestBody): Call<ResponseBody>
Make sure to import Call from Retrofit.
You set the return type of every function to be Call<ResponseBody>. This lets you parse the response. Also, you add the body parameter of type RequestBody to registerUser, loginUser and postReview methods, because these require some data to be sent to the server.
Replacing HttpUrlConnection with Retrofit
With the service methods ready, navigate to MovieDiaryApi.kt and delete the old registerUser method. You’ll implement it again to work with Retrofit. To start, add the following code:
fun registerUser(
username: String,
email: String,
password: String,
onResponse: (String?, Throwable?) -> Unit,
) {}
The method signature is the same but without the suspend modifier. The next step is also very similar. Add the following code to the function body:
val jsonBody = JSONObject().apply {
put("username", username)
put("email", email)
put("password", password)
}.toString()
JSONObject creation also stays mostly the same — you just convert it to a string at the end. To create a RequestBody from this string, add the following code:
val body = RequestBody.create(MediaType.parse("application/json"), jsonBody)
This creates an instance of the RequestBody by passing in application/json as the MIME-type and jsonBody as the content to be sent. Now that you have the data ready for transmission, it’s time to call the API. Add the following code:
apiService.registerUser(body).enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
}
override fun onFailure(call: Call<ResponseBody>, error: Throwable) {
}
})
Make sure to import Callback, Call and Response from Retrofit.
To perform the network request asynchronously, you use enqueue(). It requires a callback to handle the response or failure. Add the following to onFailure:
onResponse(null, error)
onFailure gets invoked when the request can’t be executed due to a network error or request cancellation. To handle this case, you simply pass the error to the callback, and set the content to null.
If you get any response from the server, onReponse gets invoked. To process the response, add the following code:
val message = response.body()?.string()
if (message == null) {
val error = response.errorBody()?.string()
onResponse(null, Throwable(error))
return
} else {
onResponse(message, null)
}
First, you check whether you received a valid response by extracting the message from body. If the message is null, the server responded with an error. In that case, you create a Throwable with the error message extracted from the errorBody and pass it to the callback. If the message isn’t null, you simply pass it to the callback.
Build and run the app. Try to register a new user.
If everything goes well, you’re redirected to the login screen. You can now proceed to implementing the code for login.
Implementing User Login
Implement loginUser in the same way as registerUser. Remove the suspend modifier from the method and update loginUser with the following:
fun loginUser(username: String, password: String, onResponse: (String?, Throwable?) -> Unit) {
val jsonBody = JSONObject().apply {
put("username", username)
put("password", password)
}.toString()
val body = RequestBody.create(MediaType.parse("application/json"), jsonBody)
apiService.loginUser(body).enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
val message = response.body()?.string()
if (message == null) {
val error = response.errorBody()?.string()
onResponse(null, Throwable(error))
return
} else {
}
}
override fun onFailure(call: Call<ResponseBody>, error: Throwable) {
onResponse(null, error)
}
})
}
This is the same as you did with registerUser except that you remove the email property from the JSON body because you don’t need it for the login, and replace the call to registerUser with a call to loginUser.
One other thing that’s different in loginUser() is that you must parse the token from the raw response. To do that, add the following code to the empty else block:
JSONObject(message).run {
val token = getString("token")
onResponse(token, null)
}
You create a JSONObject instance and pass it the response you got from the server. Then, you call getString("token") to get the token field from the JSON object. To finish the implementation, you pass the token to the callback.
Build and run the app again. Try to log in with the previously registered user. You’re redirected to the empty movie reviews screen if the login succeeds. It’s no fun looking at a blank screen, so implement the API call to fetch some sample movies.
Fetching the Movies
Navigate back to MovieDiaryApi.kt and scroll down to getMovies. Replace the comment with the following code:
apiService.getMovies().enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
}
override fun onFailure(call: Call<ResponseBody>, error: Throwable) {
onResponse(null, error)
}
})
There’s nothing new here. You invoke getMovies and override onResponse and onFailure. If an
error occurs, you pass it to the onReponse callback.
Handling the response will be slightly different compared to the previous two methods. Your onResponse callback expects a list of MovieReview objects, but what you get from the server is a raw ResponseBody. You’ll need to parse the raw response into a List<MovieReview>. To do that, add the following
code:
if (response.isSuccessful) {
val movieReviews = mutableListOf<MovieReview>()
JSONArray(response.body()?.string()).also { array ->
for (i in 0 until array.length()) {
array.getJSONObject(i).run {
MovieReview(
title = getString("title"),
description = getString("description"),
comment = getString("comment")
).also { movieReviews.add(it) }
}
}
}
onResponse(movieReviews, null)
}
First, you check whether the response is successful. If it is, you create a mutable list that holds MovieReview objects JSONArray(response.body()?.string()) creates a new JSONArray from the raw response. You then iterate through the entire array, creating a MovieReview from each element in the array, and finally adding it to the list. When the list is complete, you pass it to the UI via callback.
If the response doesn’t succeed, wrap it in Throwable and pass it to the callback, like so:
else {
onResponse(null, Throwable(response.errorBody()?.string()))
}
Build and run the app once more. You should have two sample reviews displayed.