Create custom Annotation using Kotlin — tutorial
This tutorial is written for beginners who have just heard the term “Annotations” on the internet or observed them while using Retrofit or other libraries, and they are wondering what is it and how can it be useful. You will learn how to create a custom Annotation and use it in the code.
Kotlin’s standard documentation says “Annotations are means of attaching metadata to code.” We all know that the information about other data is called “metadata”. Using annotations we should be able to add additional information about the other data. The other data is the “code” in this context, but the question is, what metadata we can attach and what purpose is it going to serve?
I have tried explaining it with an example here in Kotlin. Without further ado let me get you straight to the coding.
Create a new project in Android. If you don’t know this, there are a bunch of other tutorials on the internet that can help you out.
Agenda:
We will create an annotation that marks the method to be called from a background thread. If the method is called from the UI thread it will generate a warning.
4 files are created here:
- BkThread — the annotation declaration
- AnnotationUtility.kt — The helper class that checks for the thread and generates a warning.
- ClassA.kt — the class which have the annotated methods
- MainActivity .kt — calls the method in UI thread which generates a warning and then calls from a background thread shows no warning.
BkThread.kt
This is the file that declares the annotation. Here is the declaration
package com.example.testcustomannotation@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class BkThread{}
The Target tells which entity this annotation applies to, in this case, it is a Function. The other values could be CLASS, PROPERTY, CONSTRUCTOR, LOCAL_VARIABLE, etc.
The Retention determines how annotation is stored in binary output. The other values are SOURCE(Annotation isn’t stored in binary output), BINARY (Annotation is stored in binary output but not available for reflection). RUNTIME (default)tells the annotation to be stored in binary output and visible to reflection.
AnnotationsUtility.kt
The class has a class method that just checks if the method is annotated with the “@BkThread” and if it does it checks if it is being called from the UI thread. If Yes then generates the warning.
package com.example.testcustomannotationimport android.os.Looper
import android.util.Log
import java.lang.Exception
import java.lang.reflect.Methodclass AnnotationUtility { companion object{fun checkThreadWarnings(method: Method?){
if (method != null) {
try {
val bkThreadAnnotation = BkThread::class.java
val annotation = method.getAnnotation(bkThreadAnnotation)
if (annotation != null && Looper.myLooper() == Looper.getMainLooper()) {
Log.d("WARNING", method.name+" should be called from a background thread.")
}
}catch (e: Exception){
Log.d("Exception", e.toString())
} }
}
}
}
ClassA.kt
Defines a method annotates it with “@BkThread”. The method gives a call to the AnnotationUtility method to verify the thread. Giving a call to checkThreadWarning(method) manually may look an overhead but this worth it to ensure the programmers comply with the rule, especially if you are creating a framework or library.
package com.example.testcustomannotationclass ClassA { // A class in your own framework. @BkThread
fun operation1() {
AnnotationUtility.checkThreadWarnings(object{}.javaClass.enclosingMethod) // do something
}}
MainActivity.kt
It calls operation1() from the UI thread first, which generates the warning. The next call to operation1() is given from a background thread which doesn’t cause a warning.
package com.example.testcustomannotationimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import org.jetbrains.anko.doAsyncclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) val obj = ClassA()
obj.operation1() // Will generate Warning.
doAsync {
obj.operation1() // called righly, no warning
} }
}
NOTE: To use doAsync you have to add the following line build.gradle (app) file
implementation 'org.jetbrains.anko:anko-common:0.9'
That’s it. Run it.
Output:
2020-04-03 20:44:21.194 6342-6342/com.example.testcustomannotation D/WARNING: operation1 should be called from a background thread.
This annotation could be used if you are writing your framework and want to suggest users call the method from a background thread instead of a UI thread.
As I said this tutorial is just to give you a basic understanding but the annotations can be far more useful as they are exposed to the reflection at the runtime. You can make your conventions and define rules.
That’s it for now.
Happy Coding!!!