Advanced Annotation Processing

Annotation processing is a powerful tool that allows you to pack more data into your code, and then use that data to generate more code. By Gordan Glavaš.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Adding Call Invocations

Next, add this method below addService() to generate code for methods that invoke the endpoints and handle their results:

private fun TypeSpec.Builder.addMethods(): TypeSpec.Builder = apply {
	for (endpoint in data.endpointData) { // 1
	  val name = funName(endpoint.type)
	  addFunction(FunSpec.builder(name) // 1
	      .addModifiers(KModifier.SUSPEND) // 1
	      .addParams(endpoint.pathComponents, annotate = false, 
                  addBody = endpoint.type == RetroQuick.Type.POST) // 2
	      .returns(modelClassName.copy(true)) // 3
	      .addStatement("val service = %L().create(Service::class.java)", providerName) // 4
	      .addCode("""
	          |val response = service.%L(%L%L) // 5
	          |return if (response.isSuccessful) { // 6
	          |  response.body()
	          |} else {
	          |  throw Exception("Error: ${'$'}{response.code()}")
	          |}
	          |""".trimMargin(),
	          name,
	          if (endpoint.type == RetroQuick.Type.POST) "body, " else "",
	          endpoint.pathComponents.joinToString { it.paramName })
	      .build()
	  )
	}
}

This code:

  1. Generates suspend for each endpoint with the name equal to the HTTP verb.
  2. The parameters are the same as for the equivalent Service method, except they aren’t annotated. The POST has a body, while the GET doesn’t.
  3. The method returns an optional type of model class.
  4. Creates the service by statically invoking the function annotated with @RetrofitProvider.
  5. Creates the call by invoking the newly created service with the provided name and parameters.
  6. Invokes the call and return its body if it was successful. Otherwise, it throws an Exception.

Then, tie it all together by updating build():

fun build(): TypeSpec = TypeSpec.objectBuilder(className)
      .addService() // ADD THIS
      .addMethods() // ADD THIS
      .build()

All those helper methods make everything come together like Tetris blocks, assuming, of course, you’re good at Tetris. :]

All that’s left is to add some final touches.

Final Touches

Finally, go to Processor.kt and put this into process(), above the final return statement:

data.models.forEach {
  val fileName = "${it.modelName}RetroQuick"
  FileSpec.builder(it.packageName, fileName)
      .addType(CodeBuilder(data.providerName, fileName, it).build())
      .build()
      .writeTo(File(kaptKotlinGeneratedDir))
}

With this code, you invoke code generation for every model collected during processing.

Build the project again. The annotation processor will generate PersonRetroQuick.kt with this content:

package com.raywenderlich.android.retroquick


import kotlin.Int
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path

object PersonRetroQuick {
  suspend fun post(body: Person): Person? {
    val service = com.raywenderlich.android.retroquick.getRetrofit().create(Service::class.java)
    val response = service.post(body, )
    return if (response.isSuccessful) {
      response.body()
    } else {
      throw Exception("Error: ${response.code()}")
    }
  }

  suspend fun get(id: Int): Person? {
    val service = com.raywenderlich.android.retroquick.getRetrofit().create(Service::class.java)
    val response = service.get(id)
    return if (response.isSuccessful) {
      response.body()
    } else {
      throw Exception("Error: ${response.code()}")
    }
  }

  interface Service {
    @POST("person")
    suspend fun post(@Body body: Person): Response<Person?>

    @GET("person/{id}")
    suspend fun get(@Path("id") id: Int): Response<Person?>

  }
}

Now, you can use the generated code.

Open MainActivity.kt and update testCall() invocations to use PersonRetroQuick instead of returning null:

testGet.setOnClickListener {
  testCall(getResults) {
    PersonRetroQuick.get(123) // HERE
  }
}


testPost.setOnClickListener {
  testCall(postResults) {
    PersonRetroQuick.post(Person(12, "Name")) // HERE
  }
}

Using this code, you call the server endpoints and return their responses.

Finally, build and run! Press either of the buttons. You’ll see the network calls returning data from the server.

The screen with test buttons and text reponses from the server.

Where to Go From Here?

You can download the final version of this project using the Download Materials button at the top or bottom of this tutorial.

Congratulations on making it all the way through! You’ve learned even more about annotation and annotation processing, and you created a handy tool for quickly generating RESTful API code for your model classes.

You can learn more about repeatable annotations in Java and Kotlin from the official docs. It’s always a good idea to know more about code elements, too.

Hopefully, you had some fun on the way. If you have any questions or comments, please join the forum discussion below!