Last time I shared a tip on how to take great screenshots on Android. 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:
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:
In this example, I create a
Token instance for each
Callback that’s created and store a mapping between them in a
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.
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, I discovered the
@JvmSynthetic annotation. Unfortunately this annotation cannot be applied to constructors, but I did finally find a way to work around this:
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!
This tidbit was discovered on November 26, 2019.