Applying Annotations
First, though, let’s explore how we can annotate our Kotlin code, using annotations that somebody else created.
Basic Syntax
For simple use of annotations, the syntax is nearly identical to that of Java. For example, here is the Kotlin equivalent to the JUnit 4 example shown above:
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.commonsware.jetpack.hello", appContext.packageName)
}
}
As with Java, Kotlin annotations are prefixed with @
and are used to decorate classes, functions, properties, etc. Some annotations take parameters, such as the @RunWith
annotation — we will examine this more closely later in this chapter.
And, as with Java, whitespace does not matter. An annotation does not have to appear on a separate line from what it annotates. The above example could be rewritten as:
@RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest {
@Test fun useAppContext() {
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.commonsware.jetpack.hello", appContext.packageName)
}
}
Specialized Scenarios
Annotating classes, functions, and properties is fairly straightforward: just put the annotation immediately before the element that it is annotating.
However, there are a few things that can be annotated that may not be quite as obvious.
Constructors
Most Kotlin classes have a single constructor, written with the constructor parameter list immediately after the class name:
class Foo(var count: Int) {
fun something() {
count += 1
println("something() was called $count times")
}
}
As we saw back in the chapter on classes, this is a shorthand replacement for the formal approach:
class Foo constructor(var count: Int) {
fun something() {
count += 1
println("something() was called $count times")
}
}
If you need to annotate a constructor, you will need to use the formal approach, so you can place the annotation immediately before the constructor
keyword:
class Foo @MyAnnotation constructor(var count: Int) {
fun something() {
count += 1
println("something() was called $count times")
}
}
Lambda Expressions
It is possible to annotate a lambda expression, by putting the annotation immediately before the opening brace:
val lambdaLambdaLambda = @MyAnnotation { doSomethingHere() }
Under the covers, a lambda expression “expands” into an instance of a class with a single invoke()
function, with the body of the lambda expression forming the body of that invoke()
function. Annotating the lambda expression, in effect, annotates that invoke()
function.
Annotation Parameters
Some annotations take parameters, akin to a class constructor. The syntax is similar to class constructors with some minor differences. However, most of the time, they will look akin to how annotations appear in Java. So, for example, the simplest form of using the @Deprecated
annotation just passes in a String
that is the explanation for the deprecation:
@Deprecated("do not use this, because reasons")
fun thisSeemsPerfectlyFine() {
// do something
}
Named Parameters
You do not have to use named parameters with Kotlin annotations. This runs counter to Java, where there are scenarios where you have to name the parameters used in annotations.
However, if you want to use named parameters, you are welcome to do so:
@Deprecated(message = "do not use this, because reasons")
fun thisSeemsPerfectlyFine() {
// do something
}
Arrays
Sometimes an annotation will take an array of values for a parameter, rather than a single value. For that, you have two options:
- Use the
arrayOf()
global function:
@Index(arrayOf("otherId", "yetAnotherId"))
- Use array literal syntax (square-bracket notation):
@Index(["otherId", "yetAnotherId"])
Array literal syntax was added to Kotlin in version 1.2, so some older materials might not mention it as an option.
Nested Annotations
It is possible that one of the parameters to an annotation is another annotation. In that case, the nested annotation does not get the @
prefix:
@Deprecated("do not use this, because reasons", ReplaceWith("thisIsMuchBetter()"))
fun thisSeemsPerfectlyFine() {
// do something
}
This is different than Java’s approach, which retains the @
prefix on nested annotations.
Class References
Sometimes, a parameter to an annotation is a class that is to be used by the annotation processor.
For example, in Android development, Room is a library for mapping Java classes to relational database tables, specifically for Android’s SQLite database. Room has a RoomDatabase
class that needs to be annotated with a @Database
annotation. That annotation, in turn, takes an array of classes that represent the “entities” (the Java model classes that will have corresponding tables in the database).
For cases like this, we use simple ::class
notation to identify the class:
@Database(entities = [SimpleEntity::class], version = 1)
abstract class SimpleDatabase : RoomDatabase() {
// TODO add in the rest of the stuff that Room needs
}
Here, ::class
identifies the Kotlin class (i.e., SimpleEntity::class
is the Kotlin SimpleEntity
class).
In reality, this annotation needs a Java class, but that is handled by the Kotlin compiler when it processes annotations. We will explore the difference between Kotlin class objects and Java class objects later in the book.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.