Kickstarter ❤ Kotlin – Kickstarter Engineering


An exploration of our first three Kotlin classes in our Android app and how they were inspired by Swift.

In the early phases of developing our Android app at Kickstarter, we poked around the Kotlin documentation and dreamed of ways we could use this new JVM language in our app. We were, however, a small team of new Java engineers with a hefty deliverable so our Kotlin dreams stayed in the pipes — the syntax was just too different from the Java 6 we were getting used to, and we had already challenged ourselves to master the RxJava framework to build a functional, testable app.

One Android release and one Swift rewrite later, our native engineering team grew the wiser from embracing a cross-platform workflow. Kotlin began to look more familiar to us Android engineers after working in Swift as we had now experienced the benefits of a language with first-class functional programming support. Christopher Wright even took a crack at implementing functions from Learn You A Haskell For Great Good! in Kotlin and reveled at the power of the language. Our dreams of having Kotlin in the Android app started to become a tangible reality, and a year after the Android 1.0 release we introduced our first Kotlin class to our repo.

Why Kotlin?

Aside from the aforementioned first-class functional programming support (built-in lambdas, higher-order functions, operators), we felt nostalgic about Optionals, lets, and default parameter values when switching back to Java from Swift. Having safely unwrapped values and concisely declared immutable values not only soothed anxiety while attempting to write code with no side effects, but also sped up the time it took to review files e.g. not having to keep track of @Nullable values and if they were handled correctly via reviewing a pull request.

So you’re going to rewrite the Android app already?

Nope! As JetBrains intended, we have found Kotlin to interop quite nicely with our existing Android codebase. Our first Kotlin setup commit was painless and we have continued developing in Kotlin right in the mix with our Java files. We work now with the mindset of using Kotlin as we see fit to solve specific problems that Kotlin is better equipped to handle than Java 6.

Kotlin living happily amongst Java friends. This is probably incorrect, but we haven’t run into any issues yet.

Let’s dive into the first three problems we were able to solve with our Kotlin caps on.

1. I want either this value or that value

The first problem I encountered when building a feature for Android — comments on project updates — was how to refactor our CommentsViewModel in such a way to be configured with either a Project or an Update model. We had comments enabled already for projects, and since the comments UI was the same we wanted to reuse the same ViewModel logic for updates. We solved this problem in Swift by creating an Either type, which represents a choice between a Left or a Right value of different types: a project or an update. We passed this projectOrUpdate Either type along to the ViewModel, which then used the Either class’s helper methods to process the data accordingly. Yep, this stank of Kotlin potential, so we added Either.kt as our first class, with tests.

The main takeaway from Either.kt was deciding to use a sealed class over a companion object for our tagged Left and Right union. This was important because we needed to restrict our Either type to take only a Left or a Right value, not both, and a sealed class prevented even the private ability to do so (h/t Stephen Celis).

No way to instantiate both Left and Right values

Another nice benefit from writingEither in Kotlin was the ability to use higher-order functions to create useful operators, e.g.

/**
* Maps the right side of an `Either` value.
*
*
@param transform A transformation
*
@return A new `Either` value.
*/
fun <C> map(transform: (B) -> C): Either<A, C> = when(this) {
is Left -> Left(this.left)
is Right -> Right(transform(this.right))
}

We were even able to take advantage of Kotlin’s when syntax here to make a beautifully expressive function. 💅🏽

How did we use this newEither type back in Java? Check out the diff here.

2. I want a default parameter value

The most practical and least intimidating use of Kotlin I found was adding a utils method with a default parameter. Scattered throughout our codebase were repetitions of this block, which creates and sets the Animation for our WebView loading indicators:

final AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);    animation.setDuration(300L); 
animation.setFillAfter(true);
loadingIndicatorView.startAnimation(animation);

This is quite a bit of repeated code that contains the same 300L magic number for animation duration. I know, we probably should have made a helper for these animations a while ago, but we can fix it now using Kotlin to provide us with an animation of a default duration, if otherwise not specified.

@JvmOverloads  
fun appearAnimation(duration: Long = 300L): Animation {
val animation = AlphaAnimation(0.0f, 1.0f)
animation.duration = duration
animation.fillAfter = true
return animation
}
// used in Java with the default param value:
view.startAnimation(AnimationUtils.INSTANCE.appearAnimation());
// used in Java with a different param value:
view.startAnimation(AnimationUtils.INSTANCE.appearAnimation(50L));

The Kotlin nicety to notice here is the @JvmOverloads annotation which allows the Java compiler to see the method as essentially two methods: one with the default parameter, and one without.

An easy win! Pull request here.

3. I want to believe that enums can be fast

We really missed first-class enums from Swift — the type safety, the type inference, the speed. enums in Java are also handy for types, but they are quite expensive and in most use cases their values can be implemented, with resource in mind, using static constants.

Koala, our event tracking class for analytics, often provides a context with an event name to let us know from where an event was triggered. In Swift, using enums for a new string type was a no-brainer since it provided type checking:

// this is Swift
public enum CommentDialogType {
case project
case update
  var trackingString: String {      
switch self {
case .project: return "project"
case .update: return "update"
}
}
// used as a type in such:
public func trackPostedComment(project: Project,
context: CommentDialogContext) {...}

How can we provide a similar implementation using Kotlin?

Well, Kotlin has an enum class out of the box that has the type checking we crave for our context parameters. By nature I was skeptical about performance so I did some research and manual benchmarking to see how enum class performance would hold up to static classes in Java (the classic Java approach) and sealed classes (a more complex but maybe faster Kotlin approach; see “Swift Enums are more powerful:” of this nifty article).

An example of a sealed class KoalaContext implementation is:

sealed class KoalaContext {
sealed class Comments {
object PROJECT : Comments()
object UPDATE : Comments()
    fun trackingString(): String = when(this) {
is PROJECT -> "project"
is UPDATE -> "update"
}
}
}
// accessed in Java as a `Comments` type:
KoalaContext.Comments.PROJECT.INSTANCE;

An example of a static class KoalaContext implementation is:

public final class KoalaContext {  
private KoalaContext() {}
  public static class Comments {    
public static final String PROJECT = "project";
public static final String UPDATE = "update";
}
}
// accessed in Java as a `String` type:
KoalaContext.Comments.PROJECT;

An example of an enum class KoalaContext implementation is:

class KoalaContext {
enum class Comments(val trackingString: String) {
PROJECT("project"),
UPDATE("update")
}
}
// accessed in Java as a `Comments` type:
KoalaContext.Comments.PROJECT;

In the end, benchmarking via measuring the time it took to print each sealed, static, and enum Comments.Project context value 100_000 times resulted in the following:

sealed class: 		28373ms 🥇
static class: 30956ms 🥉
enum class: 29766ms 🥈

Yep, the sealed class approach was technically the winner, but we chose to favor readability and stuck with enum class— it’s faster than using static Java constants anyways in this case.

For more detail and team discussion check out the pull request here.

❤ Kotlin

Android users Chris “Milky” Wright and myself.

We are lucky to be a small team of functional programming believers who have the opportunity to work on an open sourced app day in and out. Whether you’re Kotlin-curious or a Kotlin-enthusiast follow along with or even participate in our open source repo as we continue adding all sorts of Kotlin fun!



Source link