Enjoying legacy - modern Java in Android apps
Tags: java, kotlin, android
Published: 2021-08-09 17:55:29

Kotlin is the preferred language for Android apps since 2019. Two years earlier Google announced support for Kotlin in Android Studio. Quite some time has passed since then. So, why does an article from summer 2021 still deal with *modern Java* in Android apps?

Because Java is still present in many Android apps. Particularly in those released before 2017. Sure, many have been partially or fulled converted to Kotlin. But others haven't. And, the older an app is, the older is the Java flavor being used. Apache Harmony (whose class library made its way into Android) started as Java 1.5. So, *very* old apps look like that: tons of anonymous inner classes. It took quite some time before Java 7 features like `String`s in `switch` statements or try-with-resources and `AutoCloseable` became available. Lambdas and streams of Java 8 weren't available immediately, either.

So, Java code in Android apps probably looks older than it needs to. We could of course say, as long as *that code* causes no trouble, we best leave it alone. But if it is still a vital part of the app we should treat it like we treat new code: care for it. This can mean:

- convert it to Kotlin
- make use of modern Java features

I am not going to compare these approaches, as this would be an article in its own right. 😀 Instead I will focus on the latter option. But before we dive in: this is not about Java vs. Kotlin, and this is certainly not about debating which programming language is better. I just think that any code should be as good as possible.

### Setting things up

Language versions are configured in the module-level *build.gradle* file. 

```groovy
android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_11
    targetCompatibility JavaVersion.VERSION_11
  }
  kotlinOptions {
    jvmTarget = '11'
    useIR = true
  }
  ...
```

To use Java 11 we also need to configure the [Android Gradle plugin 7.0](https://developer.android.com/studio/releases/gradle-plugin#7-0-0), which, by the way *requires* Java 11. This is done in the project-level *build.gradle* file.

```groovy
buildscript {
  ...
  repositories {
    google()
    mavenCentral()
  }
  dependencies {
    classpath "com.android.tools.build:gradle:7.0.0"
    ...
```

But why Java 11? Didn't we see Java 16 in March? Java 11 is the first long-term support (LTS) release after Java 8. Oracle stopped supporting Java 8 in January 2019. The next LTS-version will be Java 17, which is scheduled for September 2021.

### What's in stock?

As Kotliners we love to write something like
```kotlin
var s = resources.getString(R.string.app_name)
```

Or `val`, if `s` is not gong to change.

This is possible since Java **10**, too:

```java
var s = context.getString(R.string.app_name);
```

You can make `s` final if needed. What we see here is called local type inference. Nice, isn't it? Please keep in mind, though, that it doesn't work outside of blocks, methods, and constructors.

Java 11 adds `var` for lambda parameters.

```java
var list = Arrays.asList("Hello", "Android");
var result = list.stream()
    .map((var x) -> x.toUpperCase(Locale.getDefault()))
    .collect(Collectors.joining(", "));
```

There are also some new methods to `String`: `isBlank()`, `lines()`, `strip()`, `stripLeading()`, `stripTrailing()`, and `repeat()`. Sadly though, we can't use them (at the time of writing). That is because the Android class library is a combination of the Java class library and Android-specific packages. Changes to the OpenJDK class library do not become part of Android automatically. Instead they are hand-picked, sort of. Practically every API level sees some changes being ported. For example, with API level 31 we will get `compareUnsigned()` in `java.lang.Short`. It debuted in Java 9.

Even older is try-with-resources: one of the coolest, and possibly least used features of Java 7. Take a look:

```java
FileInputStream fis = null;
try {
  fis = context.openFileInput("myfile.txt");
  // do some work
} catch (FileNotFoundException e) {
  Log.e(TAG, "something went wrong", e);
}
if (fis != null) {
  try {
    fis.close();
  } catch (IOException e) {
    Log.e(TAG, "something went wrong", e);
  }
}
```

Yes, this screams **boilerplate code**. But how about this?

```java
try (FileInputStream fis = context.openFileInput("myfile.txt")) {
  // do some work
} catch (IOException e) {
  Log.e(TAG, "something went wrong", e);
}
```

In case you are wondering... No, I didn't forget about the `close()`. This is done automatically. Next...

```java
try {
  var fis = context.openFileInput("myfile.txt");
  // do some work
} catch (IOException e) {
  Log.e(TAG, "something went wrong", e);
} catch (IllegalArgumentException e) {
  Log.e(TAG, "something went wrong", e);
} catch (NullPointerException e) {
  Log.e(TAG, "something went wrong", e);
}
```

Yes, here definitely is something wrong. 

Why not this way?

```java
try {
  var fis = context.openFileInput("myfile.txt");
} catch (IOException |
    IllegalArgumentException |
    NullPointerException e) {
  Log.e(TAG, "something went wrong", e);
}
```

Since Java 7 a single `catch` block can handle more than one type of exception. This can reduce code duplication. And you no longer have an explanation for `catch`ing `Throwable`. 🤣

Another source of boiler plate code are anonymous inner classes

```java
Button b = findViewById(R.id.button);
b.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    // do some work
  }
});
```

We can shrink this to:

```java
Button b = findViewById(R.id.button);
b.setOnClickListener(view -> {
  // do some work
});
```

As you have seen, some of the prejudices Java is still facing are (to some extent) no longer valid. It is up to developers to modernize their code. There's quite a lot you can do.

### Conclusion

Modernizing old Java code is undoubtedly worth the effort. First, it becomes much easier to read. Second, you will be able to remove tons of boilerplate code. And third, your app will become less error-prone. Don't believe me? Be honest, does your code really put `close()` inside `try`-`catch`? ...well, you should. And with try-with-resources you get it for free.