Matt's Tidbits #45 - Creating a private token class
Tags: mattstidbits, android, kotlin, java
Published: 2019-12-03 17:24:12

[Last time I shared a tip on how to take great screenshots on Android.](https://medium.com/@matthew.b.groves/matts-tidbits-44-how-to-take-great-screenshots-on-android-e42b5572e650) This week I wanted to share an interesting story about trying to create a private token class and the solution I came up with.

First, a little bit of background. In the project I’m working on, there’s a setup like this:
{% gist https://gist.github.com/mpeng3/e04339103c3f910fab72375bf1c18015 %}

As you may have noticed, there’s one problem with this code (see comment on line 19) — there’s currently no way to unregister the callback that gets passed into the `LowLevelLibraryInterface` because it’s created anonymously. Why would we want to unregister this? We may want to call `stopSomething()` for a variety of reasons — we want to cancel this due to direct user action, or, perhaps more critically, we called this from an activity/fragment that is being destroyed and we want to clean up after ourselves so we don’t leak any memory.

So, clearly we need some way to keep track of these callbacks and be able to retrieve them later. The first step will be to change the instantiation so it’s no longer anonymous, and, we’ll need to notify callers of `startSomething` with what callback corresponds to their request (so they don’t unregister a callback for someone else).

The simplest implementation I came up with was this:
{% gist https://gist.github.com/mpeng3/5b1f7688b94a664446838618ddf629be %}

In this example, I create a `Token` instance for each `Callback` that’s created and store a mapping between them in a `HashMap`. The `Token` that gets generated is then returned to the caller. Why not just return the callback to them? Because then they could theoretically call it themselves, which could lead to serious issues down the road. Additionally, I created a special type (`Token`) instead of using int/string/etc. — this is to prevent clients from being able to accidentally use a token with a value that might correspond to another invocation of this method.

This version is pretty good, but I still wasn’t completely happy with it. The `Token` class is publicly available, and anyone can create these, and they really have no reason to.

So, I tried creating a more locked-down version of the `Token` class — this time using [Kotlin’s Sealed Classes feature.](https://kotlinlang.org/docs/reference/sealed-classes.html)
{% gist https://gist.github.com/mpeng3/d6d39ca3834348db309f2e29ebe6b25d %}

This version contains several improvements, namely that only code in this file can construct instances of `TokenImpl`, although everyone is still able to see and use the `Token` type. There’s one problem with this solution though — Java code in the same package is still able to see and use the TokenImpl() constructor.

After some research and, honestly, the fortunate timing of reading [this article by Jake Wharton](https://jakewharton.com/public-api-challenges-in-kotlin/), I discovered the `@JvmSynthetic` annotation. Unfortunately this annotation cannot be applied to constructors, but I did finally find a way to work around this:
{% gist https://gist.github.com/mpeng3/891d3b4b39540086cfc0b5d4108e8573 %}

In this version, I finally have what I’m looking for! Defining the class and constructor as private prevents them from being visible outside of this file. And, the `create()` method with `internal` visibility lets me call this method from any places in this module that can see the `TokenImpl` class (in this case, only the current file). Lastly, the `@JvmSynthetic` annotation hides the `create()` method from Java code so it can’t be called from there either.

I hope you learned something interesting and useful in this week’s tidbit! If you can think of a better/different/cleaner way to solve this problem (or know of an existing bug against Kotlin/the JVM for this issue), please let me know in the comments below! And, please follow me on Medium if you’re interested in being notified of future tidbits.

Interested in joining the awesome team here at Intrepid? [We’re hiring!](https://www.intrepid.io/careers)

*This tidbit was discovered on November 26, 2019.*