Skip to main content

Annotation processor: Say less, mean more

Annotation processor: Say less, mean more
I’ve always been curious on what is behind an annotation. As much as they made my angry, believe me they are so fun. This is my experience…

I’ve always been curious on what is behind an annotation. As much as they made my angry, believe me they are so fun. This is my experience as a beginner on the Annotation Processing.
I will give my own definition for an annotation:
An annotation is just a way to mark a class, field, another annotation, method etc. Why? It just tells that the marked component has a special attribute. But how do you handle it?
The way to handle an Annotation is through generating code at compile time. This has always intrigued me. Why? This depends on the use case. Have you ever thought how Dagger2 knows what dependencies you are using? Or perhaps how Butterknife knows how to bind views or set an onClickListener? Yep, generating code at compile time. The process of generating code at compile time to handle the annotations is called Annotation Processing . I will do a simple use case, explaining all what’s happening.
The use case:
I hate to type:
activity.getSupportFragmentManager()
.beginTransaction()
.replace(R.id.someId,SomeFragment.newInstance())
.commit()
for each fragment that I need to start. Someone here would be right to say that what do we need the annotations for, but this article is for “academical” reasons, not for providing some best practice or refactor.
Setup:
First of all, you must know that creating an annotation and handling it, you need separated modules. That’s because we need to tell gradle that there is some code that needs to be imported and the other code that works as a compiler. To create a new module in Android Studio go to File->New->Module and select Java Library.
Note, Android Library is not necessary in this case. Choose Android library when you are creating a custom view class or whatever.
So let’s go :
I have created 2 more modules except from my current Android app module. I named my modules Browser since they I will basically navigate through app fragments.
Let’s start with the easy one: Create the desired annotation:
There are 2 important things here: The @Target which does the check if I have annotated my desired component, in my case a CLASS . If I annotate a method or a filed it will show an error, and the @Retention which determines how an annotation is stored in binary output (from Kotlin documentation). There are 3 kinds of AnnotationRetention s:
For the moment, I’m just letting it to source because I don’t need any of those other options.
Go to the browser_compiler and open its build.gradle to import these libraries:
We will explain other dependencies later. For the moment just notice importing the above module in order to handle that Annotation in the compiler module.
Note: don’t forget to add the apply plugin: ‘kotlin-kapt’ ,otherwise, forget about a successful build.
Create a class and extend AbstractProcessor (overriding the methods of course). Since I am using kotlin metadata library, I am extending the KotlinAbstractProcessor but there is no difference between them, except from accessing some fields directly on this case.
This is why we imported the other module here, to access the annotation.
Work:
The work will happen in the process method. This is where the magic happens:
This is where I am talking to the Kotlin compiler and telling it that if there is any thing annotated with @BindFragment annotation apart from a class, show some error message.
Our elementUtils.getPackageOf(annotatedElement).toString() will just provide us the annotated class package name, while the annotatedElement.simpleName().toString() will just provide us the annotated class name. Now let’s jump to the startClassGeneration method. I am using the KotlinPoet library to generate kotlin code on the compile time, but it is not mandatory, you can also type simple strings as long as it is correctly used without and without compile errors. This is the code:
You can read the documentation on what is doing one and what is doing the other.
Details are important:
We must annotate this generator class with the @AutoService(Processor::class) . It just creates a registration file for this custom processor. Not providing it will force you to include your processor manually in the META-INF directory. That way it can be accessed through all the project.
Full processor file:
The moment of truth:
Go to your build.gradle module app and import both your modules, the browser module as an implementation and the browser_compiler as the processor using kapt. Note to include kotlin-kapt here also.
Annotate your Fragments with your created annotation:
Annotate your second fragment, which you will navigate after a button click in the first fragment:
Hit BUILD on your IDE and go to main activity to open the FirstFragment
And the FirstFragment , on the button click will have this code:
Done!
Conclusion:
There is plenty to learn on generating code at compile time and this includes me too. I want to start creating a real library, once I feel confident on this.
Happy processing!

Popular posts from this blog

Modularizing your Android app, breaking the monolith (Part 1)

Inspired by a Martin Fowlers post about Micro Frontends, I decided to break my monolithic app into a modular app. I tried to read a little more about breaking monolithic apps in Android, and as far as I got, I felt confident to share my experience with you. This will be some series of blog posts where we actually try to break a simple app into a modularized Android app.

Note: You should know that I am no expert in this, so if there are false statements or mistakes please feel free to criticize, for the sake of a better development. 

What do you benefit from this approach:
Well, people are moving pretty fast nowadays and delivery is required faster and faster. So, in order to achieve this, modularising Android apps is really necessary.You can share features across different apps. Independent teams and less problems per each.Conditional features update.Quicker debugging and fixing.A feature delay doesn't delay the whole app. As per writing tests, there is not too much difference about…

What I learned from Kotlin Flow API

I used to check the docs and just read a lot about flows but didn't implement anything until yesterday. However, the API tasted really cool (even though some operations are still in Experimental state).Prerequisites: If you don't know RxJava it's fine. But a RxJava recognizer would read this faster.Cold vs Hot streamsWell, I really struggled with this concept because it is a little bit tricky. The main difference between cold and hot happened to be pretty simple: Hot streams produce when you don't care while in cold streams, if you don't collect() (or RxJava-s equivalent subscribe()) the stream won't be activated at all. So, Flows are what we call cold streams. Removing the subscriber will not produce data at all, making the Flows one of the most sophisticated asynchronous stream API ever (in the JVM world). I tried to make a illustration of hot and cold streams: Since I mentioned the word asynchronous this implies that they do support coroutines also. Flows vs…

From Gson to Moshi, what I learned

There is no doubt that people are getting away from GSON and I agree with those reasons too. The only advantage GSON has over other parsing libraries is that it takes a really short amount of time to set up. Furthermore, the most important thing is that Moshi is embracing Kotlin support.

First let's implement the dependency:
implementation("com.squareup.moshi:moshi:1.8.0") It's not a struggle to migrate to Moshi. It's really Gson look-a-like. The only thing to do is annotate the object with @field:Json instead of @SerializedName (which is Gsons way for JS representation):

data class User( //GSON way @SerializedName("name") val name: String, @SerializedName("user_name") val userName: String, @SerializedName("last_name") val lastName: String, @SerializedName("email") val email: String ) data class User( //Moshi way @field:Json(name = "name") val name: String, @field:Json(name = "user_name…