The errors for both Repository.getNextGuestId() and Repository.addUser(...) are the same: "Non-static cannot be accessed from a static context."

Now let's take a look at one of the Kotlin files. Open the file Repository.kt.

We see that our Repository is a singleton that is declared by using the object keyword. The problem is that Kotlin is generating a static instance inside our class, rather than exposing these as static properties and methods.

For example, Repository.getNextGuestId() could be referenced by using Repository.INSTANCE.getNextGuestId(), but there's a better way.

We can get Kotlin to generate static methods and properties by annotating the public properties and methods of the Repository with @JvmStatic:

The @JvmStatic annotation tells the Kotlin compiler to generate a static version of the annotated member or method.

If we switch back to UseCase.java, the properties and methods on Repository are no longer causing errors, except for Repository.BACKUP_PATH. We'll come back to that later.

For now, let's fix the next error in the registerGuest() method.

Let's consider the following scenario: we had a StringUtils class with several static functions for string operations. When we converted it to Kotlin, we converted the methods into extension functions. Java doesn't have extension functions, so Kotlin compiles these methods as static functions.

Experienced Java developers might recognize StringUtils as an identifier in the Apache Commons set of libraries. Since Apache Commons is rarely used for Android applications, the StringUtils that we are using in this CodeLab is completely unrelated.

Unfortunately, if we look over at the registerGuest() method inside UseCase.java, we can see that something isn't quite right:

The reason is that Kotlin places these "top-level" or package-level functions inside a class whose name is based on the filename. In this case, because the file is named StringUtils.kt, the corresponding class is named StringUtilsKt.

We could change all our references of StringUtils to StringUtilsKt and fix this error, but this isn't ideal because:

There may be many places in our code that would need to be updated.

The name itself is awkward.

So rather than refactor our Java code, let's update our Kotlin code to use a different name for these methods.

Open StringUtils.Kt, and find the following package declaration:

package com.google.example.javafriendlykotlin

We can tell Kotlin to use a different name for the package-level methods by using the @file:JvmName annotation. Let's use this annotation to name the class StringUtils.

Now, if we look back at UseCase.java, we can see that the error for StringUtils.nameToLogin() has been resolved.

Unfortunately, this error was replaced with a new one about the parameters being passed into the constructor for User. Let's continue to the next step and fix this last error in UseCase.registerGuest().

The @JvmName annotation is a powerful tool that influences how Kotlin names classes, properties, and methods in the bytecode it generates for the Java Virtual Machine (JVM). We'll discuss another of its uses later in this codelab.

Default values aren't supported in the Java programming language. To fix this, let's tell Kotlin to generate overloads for our constructor with the help of the @JvmOverloads annotation.

First, we have to make a slight update to User.kt.

Since the User class only has a single, primary constructor, and the constructor does not include any annotations, the constructor keyword had been omitted. Now that we'd like to annotate it, however, the constructor keyword must be included:

If we switch back to UseCase.java, we can see that there aren't any more errors in the registerGuest function!

Our next step is to fix the broken call to user.hasSystemAccess() in UseCase.getSystemUsers(). Continue on to the next step for that, or continue reading to dig deeper into what @JvmOverloads has done to fix the error.

@JvmOverloads

To better understand what @JvmOverloads does, let's create a test method in UseCase.java:

This is an interesting error! If you use your IDE's autocomplete feature on the class User, you will notice hasSystemAccess() was renamed to getHasSystemAccess().

To fix the problem, we'd like to have Kotlin generate a different name for the val property hasSystemAccess. To do this, we can use the @JvmName annotation. Let's switch back to User.kt and see where we should apply it.

There are two ways we can apply the annotation. The first is to apply it directly to the get() method, like this:

The alternate method is particularly useful for properties that are using a default, implicitly-defined getter. For example:

@get:JvmName("isActive")
val active: Boolean

This allows the getter's name to be changed without having to explicitly define a getter.

Despite this distinction, you can use whichever feels better to you. Both will cause Kotlin to create a getter with the name hasSystemAccess().

If we switch back to UseCase.java, we can verify that getSystemUsers() is now error free!

The next error is in formatUser(), but if you'd like to read more about Kotlin getter naming convention, continue reading here before moving on to the next step.

Getter and Setter Naming

When we're writing Kotlin, it's easy to forget that writing code such as:

val myString = "Logged in as ${user.displayName}")

Is actually calling a function to get the value of displayName. We can verify this by going to Tools > Kotlin > Show Kotlin Bytecode in the menu and then clicking the Decompile button:

String myString = "Logged in as " + user.getDisplayName();

When we want to access these from Java, we need to explicitly write out the name of the getter.

In most cases, the Java name of getters for Kotlin properties is simply get + the property name, as we've seen with User.getHasSystemAccess() and User.getDisplayName(). The one exception is properties whose names begin with "is". In this case, the Java name for the getter is the name of the Kotlin property.

For example, a property on User such as:

val isAdmin get() = //...

Would be accessed from Java with:

boolean userIsAnAdmin = user.isAdmin();

By using the @JvmName annotation, Kotlin generates bytecode that has the specified name, rather than the default one, for the item being annotated.

This works the same for setters, whose generated names are always set + property name. For example, take the following class:

class Color {
var red = 0f
var green = 0f
var blue = 0f
}

Let's imagine we'd like to change the setter name from setRed() to updateRed(), while leaving the getters alone. We could use the @set:JvmName version to do this:

UseCase.formatUser() uses direct field access to get the values of the properties of a User object.

In Kotlin, properties are normally exposed via getters and setters. This includes val properties.

It's possible to change this behavior by using the @JvmField annotation. When this is applied to a property in a class, Kotlin will skip generating getter (and setter for var properties) methods, and the backing field can be accessed directly.

Since User objects are immutable, we'd like to expose each of their properties as fields, and so we'll annotate each of them with @JvmField:

If we look back at UseCase.formatUser() now we can see that the errors have been fixed!

@JvmField or const

With that, there's another similar looking error in the UseCase.java file:

Repository.saveAs(Repository.BACKUP_PATH);

If we use autocomplete here, we can see that there is a Repository.getBACKUP_PATH(), and so it may be tempting to change the annotation on BACKUP_PATH from @JvmStatic to @JvmField.

Let's try this. Switch back to Repository.kt, and update the annotation:

object Repository {
@JvmField
val BACKUP_PATH = "/backup/user.repo"

If we look at UseCase.java now, we'll see the error went away, but there's also a note on BACKUP_PATH:

'const' might be used instead of '@JvmField'

In Kotlin, the only types that can be const are primitives, such as int, float, and String. In this case, because BACKUP_PATH is a string, we can get better performance by using const val rather than a val annotated with @JvmField, while retaining the ability to access the value as a field.

Let's change that now in Repository.kt:

object Repository {
const val BACKUP_PATH = "/backup/user.repo"

If we look back at UseCase.java we can see there's only one error left.

The final error says Exception: 'java.io.IOException' is never thrown in the corresponding try block.

If we look at the code for Repository.saveAs in Repository.kt, we see that it does throw an exception, though. What's going on?

Java has the concept of a "checked exception". These are exceptions that could be recovered from, such as the user mistyping a file name, or the network being temporarily unavailable. After a checked exception is caught, the developer could then provide feedback to the user on how to fix the problem.

Since checked exceptions are checked at compile time, you declare them in the method's signature:

Kotlin, on the other hand, does not have checked exceptions, and that's what's causing the problem here.

The solution is to ask Kotlin to add the IOException that's potentially thrown to the signature of Repository.saveAs(), so that the JVM bytecode includes it as a checked exception.

We do this with the Kotlin @Throws annotation, which helps with Java/Kotlin interoperability. In Kotlin, exceptions behave similar to Java, but unlike Java, Kotlin only has unchecked exceptions. So if you want to inform your Java code that a Kotlin function throws an exception, you need to use the @Throws annotation to the Kotlin function signature Switch to the Repository.kt file and update saveAs() to include the new annotation: