Learning Kotlin by “Mistake”

I recently wrote about how the Android engineering team at Udacity has embraced learning Kotlin through experimentation and making “mistakes”.

 

Check it out here:

https://engineering.udacity.com/learning-kotlin-by-mistake-b99304b7d724

 

. . .

Thoughts, questions, tips on learning Kotlin? Comment below or share on social media.  I’d love to continue the conversation.

 

I love to meet/talk/discuss with others so if you have feedback or want to chat you can follow me on Twitter, YouTubeMedium, or subscribe here to stay up to date on the latest posts from my blog.

As our individual and collective team understanding of Kotlin develops, we inevitably review & revisit code thinking things like:

“I’ve learned 3 new ways to write this”

“I prefer a different syntax/convention now”

“What was I thinking when I wrote this the first time?”

As these conversations arise, there is potential to feel as if we’ve made a “mistake”; that by not knowing the “ideal” pattern or trying something different we’ve somehow wasted time by writing a sub-optimal solution.

Photo by Estée Janssens on Unsplash

Ultimately, we’ve all been in this position.

We learn how to do something, write the code, continue learning and eventually discover a new/different/maybe-better way of solving the same problem. We call the previous iterations a “mistake” and discount what we learned during the process.

“Mistakes” are learning opportunities

We’ve worked to encourage these “mistakes” and embrace the exploration and experimentation that may lead to these conversations. It’s all too easy to simply apply our familiar patterns & conventions from Java to our Kotlin codebase, but in doing so we lose out on some of the opportunities Kotlin provides.

If we find that our previously held understanding was mistaken, it means we’ve increased our collective understanding and most likely learned a good deal along the way.


Learn from our “mistakes”

As we continue with Kotlin, we’ve made a number of “mistakes” that have lead us to a deeper understanding of the language, and ultimately to better code. The following are a few instances where our previously held understanding of the language was challenged and developed over time.


Chain everything into a single statement

Kotlin provides a number of functional constructs that allow the concise chaining of operations into a single statement or expression.

One such construct is the apply function

When I first started using apply, I tended to throw as many related statements as I could into the apply block aiming to remove as many redundant references to the receiver object as possible.

Here’s an example of using apply that at one point seemed great, but, in my opinion, now feels like a misuse of the function.

Intent(this, MainActivity::class.java).apply {
putExtra("extra1", "foo")
putExtra("extra2", "goo")
startActivity(this)
}

It avoids storing a reference to the Intent, but mixing the configuration logic with the usage of the Intent makes the overall purpose of the code a bit harder to quickly discern.

That led to me looking deeper into the other, similar functions letwithrun, and also. I tried re-writing it like this:

Intent(this, MainActivity::class.java).apply {
putExtra("extra1", "foo")
putExtra("extra2", "goo")
}.run {
startActivity(this)
}

By removing startActivity(this) out of the apply block, it more clearly separates the configuration of the Intent and its usage. Unfortunately, moving the statement into run adds a bit of verbosity that Kotlin is generally good at avoiding.

For this type of situation, I’ve now moved away from trying to overuse these Kotlin functions, and simply storing the Intent in a val and referencing it in a separate statement. It’s 1 extra line of code, but much easier to quickly understand (in my opinion).

val newIntent = Intent(this, MainActivity::class.java).apply {
putExtra("extra1", "foo")
}
startActivity(newIntent)

If you still prefer everything be chained together you could try this as well

startActivity(Intent(this, MainActivity::class.java).apply {
putExtra("extra1", "foo")
})

I may have gone through several iterations of solving a similar problem, and abused some functions along the way, but I now have a much better understanding of the differences between letapplyrun, and with as well as an understanding of how those functions are actually implemented.

Ultimately this is a small example of a larger problem:

It’s very easy, tempting even, to make Kotlin code as concise as possible but in doing so, it may become less understandable. Finding a balance between brevity and readability is an ongoing and important part of diving into Kotlin.


Always use a CompanionObject for constants

There is no static in Kotlin

When using the Java-to-Kotlin conversion tool, your static variables and methods will be converted to properties and functions on a CompanionObject

Since this is the default behavior provided by the tooling, for a long time I didn’t question whether using a CompanionObject for these use cases was always the best option.

After working in several different mixed, Java & Kotlin, codebases I started to see a number of situations like this:

class Foo() {
companion object {
private val KEY1 = "key1"
private val KEY2
= "key2"
}
}

A Kotlin class converted from Java would contain a CompanionObject for the sole purpose of containing the previously static values.

Eventually I came to the realization that in Kotlin, we are no longer bound to a strictly object-oriented world anymore and our constant values don’t necessarily have to be scoped to a class.

With Kotlin it’s possible to have the following within Foo.kt:

private const val KEY1 = "key1"
private const val
KEY2 = "key2"

class
Foo()

We can define our constants as top-level properties using const val. There is then no need for a CompanionObject and any of the pitfalls they may entail.

Learning to embrace this less object-oriented approach was a very freeing experience and led to other experimentation and learning with top-level declarations that we will explore shortly.


The Java -> Kotlin conversion tool is enough

Kotlin has a pretty great interop story with Java. Additionally, Android Studio has an extremely useful Java to Kotlin conversion tool that makes it trivial to incorporate Kotlin into your codebase by converting an existing Java file with the click of a button.

All of that said, we’ve learned from experience (sometimes the hard way) that simply clicking `Convert Java File to Kotlin File`is not enough. There are a number of things to consider beyond what the conversion tool provides.

  • Think about nullability and avoiding !! whenever possible.
  • Prefer val to var. The conversion tool isn’t always able to convert your existing code as-is to make full use of val over var.
  • Does your converted class now have a CompanionObject? Does it need one?
  • Do you have overloads or builders that could be replaced with default/named parameters?
  • Converted Java doesn’t take into account the functional operators available or idiomatic Kotlin conventions such as higher-order functions or extension functions.

Not every converted file will require you to address all of these areas. However, sometimes the conversion tool is just the first step and to get the full benefit of Kotlin considerable time may be needed to manually convert your code.

“reexamining our usage of null; enforcing immutability; and applying functional operators allowed us to write safer, more concise code”

For us, this provided a huge opportunity to really sink our teeth into the language and explore what was possible.

Closely reexamining our usage of null; enforcing immutability; and applying functional operators allowed us to write safer, more concise code which is our primary reason for moving to Kotlin.


Also want to mention Java -> Kotlin interop here.

Depending on your conversion strategies and overall Kotlin adoption some of the following items might impact how you think about the above items

  • Platform Types: If your Kotlin code is interacting with Java, you may need to consider how null enforcement is handled since types coming from Java can be null or non-null. One strategy for handling this is to make thorough use of nullability annotations on your Java types.
  • When using CompanionObjects you may want to consider whether using the @JvmStatic and @JvmField annotations are beneficial to your code. These annotations can allow you to have truly Java-static versions of your CompanionOjbect properties and functions.
  • If the idea of default & named parameters is appealing, keep in mind that, by default, when calling a method from Java that has default parameter values only 1 version of the method (with all parameters required) is available to you. The @JvmOverloads annotation can be applied to generate multiple overloads for the Java side, but this can be cumbersome to use so it’s utility will likely depend on your use case.

For more on Java-Kotlin interop, check out the documentation:Calling Kotlin from Java — Kotlin Programming Language
Having multiple files which have the same generated Java class name (the same package and the same name or the same…kotlinlang.org
Calling Java from Kotlin — Kotlin Programming Language
Kotlin is designed with Java Interoperability in mind. Existing Java code can be called from Kotlin in a natural way…kotlinlang.org


lateinit var foo:SomeType is the best we can do for lifecycle dependent initializations

This was a common misconception for our team for quite a while.

We would often come across places where we would only initialize an object once, but that initialization needed to be deferred until onCreate or some other point beyond the initial object creation.

Ideally, we wanted to define these properties as val to properly enforce immutability, but with the lifecycle dependency this didn’t seem feasible.

Using lateinit lets us defer the initialization until a later point, and will throw an exception if accessed before initialization has occurred. This may be closer, semantically, to our ideal usage but leaves the following questions:

  1. Has this property been initialized when I want to access it?
  2. Has this property changed? Since it’s now a var there’s no insurance that it hasn’t been reassigned.

Ideally, we could express the property as a val and still defer until to the appropriate point in the Android lifecycle

This is where Delegated Propertiescome in, and where our team had another “ah ha!” moment.

By creating a custom delegate, we can defer the initialization until the appropriate time and define the property as a read-only val.

Once such delegate is lazy which will initialize the property on first access

This allows us to move from

lateinit var foo:SomeType

to

val foo:SomeType by lazy { <creation & initialization here> }

We now have the desired read-only semantics, and know that our object will be available when needed.

You can write custom delegates as well. This is useful if you want to provide a more readable delegate name, provide a delegate for generic types, or have initialization logic that is dependent on the Android Activity lifecycle.

The following is a custom delegate we’ve been using to simplify how we handle binding objects when using Android’s databinding:


top-level functions are always preferable to using a CompanionObject function

As mentioned earlier, Kotlin enables us to break free from strict object-oriented programming and embrace pure functions.

One area where we’ve really experimented with this is in our usage of helper functions.

For example, it’s a common Android convention in Java to define a static startActivity method for our activities. When converted to Kotlin, this approach would leverage a CompanionObject with a function. However, in a Kotlin code base we don’t necessarily need to tie ourselves to a companion, and instead can choose instead to just define a top-level function to handle the same functionality.

This eliminates the need for a CompanionObject, but also adds to the global namespace since top-level functions are available to the entire package unless marked private.

This is somewhat subjective, but it might not be preferable to have everything publicly available; particularly if you’re writing a library and want to maintain an isolated, self-contained api.

In this case, scoping to the class in question can be a useful convention. Your team may also prefer the convention of accessing the function in more of the traditional Java “static” manner in which case again scoping the function to a CompanionOjbect might be preferable.

The approach here may also depend on whether your code base is mixed Java & Kotlin, or maybe 100% Kotlin.

We’ve found that the more functional approach feels a bit nicer in our fully Kotlin project than it does in our mixed code base. This is primarily because when calling a Kotlin function from Java, whether from a CompanionObject or as a top-level function, there are hoops to jump through such as needing to mark functions with @JvmStatic or needing to import the generated Kotlin file that contains the top-level function.

We are still experimenting with different patterns and conventions for leveraging pure functions versus “static” ones, but, to date, our trial and error has resulted in some useful results and a lot on lessons learned.


Go forth, and make “mistakes”

Hopefully you’ll find some of our previous “mistakes” enlightening and will be encouraged to continue pushing the bounds of your understanding of Kotlin.

Experimenting with a new pattern, discovering a new function, learning the pros/cons of different implementations; these drive the learning process.

Photo by Sai Kiran Anagani on Unsplash

As the Android community learns to leverage Kotlin together, we should be embracing the opportunity to discover new skills/patterns/conventions and happily discovering that our previous understanding was “mistaken”.

We hope to learn from your “mistakes” soon!


I love to meet/talk/discuss and help where I can. If you want to chat or ask a question you can follow me on Twitter, YouTube, Instagram and Facebook.

Check Out My YouTube Channel

Adopting Kotlin

I recently wrote about how we adopted Kotlin as the main development language for Android on the mobile team at Udacity.

Nate Ebel on Twitter

Just in time for @kotlinconf See how we’ve been using @kotlin for #androiddev on the mobile team @udacity https://t.co/Am0wFX9DXx

Check out the original posts here:

Udacity Engineering Blog

Udacity India Blog


If you’re an Android developer, you’ve most likely heard about Kotlin sometime during this past year.

For those who aren’t familiar, Kotlin is a programming language developed by JetBrains that targets the JVM and is 100% compatible with Java for Android development.

*Kotlin is actually capable of much more than just Android development. For more info checkout the language webpage.

On the mobile team here at Udacity, we’ve been using Kotlin in production for ~9 months, and have adopted it as our primary development language moving forward.

Recently, we’ve been asked a number of times about our usage of the language and why we’ve invested in it, so we thought we would share a bit about the following:

  • Why we’ve invested in Kotlin
  • Our adoption process
  • How are we using Kotlin today
  • Tips for incorporating Kotlin into your project

Why have we invested in Adopting Kotlin?

If you ask each member of our team this question, you’d get a slightly different response, but the general consensus would likely sound something like this:

“Kotlin provides a more enjoyable developer experience by allowing us to express our features & ideas using safer, more concise code. This, in turn, enables us to build a better product for our users”

More concretely, we believe Kotlin provides several key benefits to our team:

  1. Improved crash rate
  2. Reduction in lines of code written
  3. Decreased time spent in code review
  4. Renewed excitement around experimentation and innovation

Ultimately, our goal is to provide our users with the best learning experience possible. We believe these items help us do that.


How did we start adopting Kotlin?

Our adoption of Kotlin started through the recognition of “pain points” that we experienced on a regular basis. During code review, comments like this started to crop up often:

“this 10 line block could be done in 3 lines using Kotlin”
 “it would be great here if we could avoid checking for null in 4 different places”

Having experimented with Kotlin outside Udacity, we knew the language might help us solve these problems and build better Android apps. Because of this, it wasn’t long before we had our first Kotlin PR

A small feature was implemented in Java, and a pull-request opened; but along with that PR another was created that provided the same feature; only written in Kotlin. The Kotlin PR wasn’t merged in that instance, but it demonstrated that we could easily integrate Kotlin into our existing app without compatibility issues or a slowdown in development time.

That was the jumping off point. We decided we would migrate a single, small feature in the app to Kotlin and see how it did in production.

We started by migrating our Settings screens in a separate develop branch. This branch received a lot of internal testing while we evaluated closely for 2 types of issues:

  1. Usability issues: what impact did Kotlin have on crashes/bugs?
  2. Development process: did Kotlin cause CI issues, slow down our build, or slow down feature development?

When nothing arose, we shipped the new Kotlin feature to users and crossed our fingers. Thankfully, as we expected, no major issues were found. We were now happily using Kotlin in production.

After that, we slowed down a bit. We loved using Kotlin, but recognized the potential risks of going all-in on an unofficially supported language. We continued to add/convert some tests to Kotlin, but no new feature code was using it.

Then Google announced support for Kotlin as a first-class language for Android development.


Team buy in

Google’s backing was the push we needed to fully embrace Kotlin.

We met with our PM and expressed our desire/plan to adopt Kotlin as our primary development language. Understandably, we were met with a healthy dose of pragmatism and asked a few important questions:

  • Is the language stable/safe to be using now?
  • Will it slow down development?
  • Are there tangible benefits?

“what is the value prop?”

We benefitted here from having already successfully shipped production Kotlin, and could point to ourselves as an example of production usage
of the language.

After expressing our beliefs that we could write safer, less error-prone code with a Google-backed language, and that it wouldn’t slow down our development process we received the thumbs up we were looking for.


Migration plan

From this point we decided Kotlin would be our language going forward and created a migration plan:

  1. All new dev work in Kotlin
  2. If anything larger than a small bug fix must be done to a class, consider migrating the class to Kotlin
  3. Start migrating existing code

Where feasible, the in-progress features began using Kotlin, and all new work began being written with it.

Our plan for migrating existing code, was to tackle the low hanging fruit first (utilities, helpers, isolated classes) then work towards migrating enough code that the majority of our daily development would be in Kotlin.

We identified the packages we thought were good candidates, and spent ~ 2 months migrating code while still pressing forward with new feature development.

For most Java classes, we started by using the Java to Kotlin conversion tool within Android Studio. We would then make manual adjustments focusing on a few things:

  1. Avoiding the use of !! operator
  2. When possible, convert helper methods to top-level functions
  3. Avoid companion object where possible
  4. Reorganize the generated code
  5. Expressing code in a more Kotlin idiomatic way

Reevaluating our handling of nullability was probably the most time consuming part of these manual adjustments, but lead to some of the greatest wins regarding stability and code maintenance.

In many places we focused on leveraging statements such as object?.let {...} rather than using if(object != null) { ... }.

We also tried to leverage the great collections stdlib that Kotlin provides. These apis allowed us to replace large blocks of code with a few lines of concise functional code.

When used together, these two features of Kotlin allow us to write code like this:

val recentQueries:List<String?>? = listOf()
val recentsToDisplay = recentQueries
?.filterNotNull()
?.filter{ it.isNotBlank() }
?.take(5)
?: listOf()

This snippet is short, easy to follow and has fewer possible points of error than the equivalent Java code would have.


Where are we today?

Today we have about 1/3 of our main codebase written in Kotlin. As we touch features we continue to migrate that code, but we’ve slowed down on migrating existing code.

This is largely due to us shifting dev cycles to several experiments that are being carried out in separate codebases.

These new codebases are 100% Kotlin, and will ideally stay that way.

Working in an exclusively Kotlin codebase also means we don’t have to concern ourselves as much with java interoperability. This allows us to more fully explore the language.

Our aim is to discover and try what is possible with the language, rather than simply writing our familiar Java code; but in Kotlin.

We’ve enjoyed experimenting with new patterns that may have been not possible, or too cumbersome in Java. For example, the freedom to have top-level functions or easy property delegation have lead to cleaner and more semantically correct code.


Tips for adopting Kotlin

Having gone through the process of incorporating Kotlin into an existing codebase, migrating a large portion of the codebase to Kotlin, and starting a new project from scratch using 100% Kotlin; here are a few tips that we’ve learned along the way.

  • If new to the language, start with the conversion tool
  • Don’t be afraid to further modify the converted code
  • Starting with tests can help work out any issues with your build and start giving a feel for the language, but writing feature code will give a more comprehensive picture of how it can affect your code
  • Don’t go wild trying to write “idiomatic” Kotlin. As you progress with the language, it’s likely your opinion of whats readable vs what’s possible will evolve.

Just because you can doesn’t mean you should

  • As you migrate existing code, re-visit previously converted code from time to time. You may find that newly discovered conventions/patterns could be applied.
  • Don’t forget to thoroughly test any interop layers between Java and Kotlin. Nullability issues can crop up during runtime when dealing with nullableplatform types.

Explore, experiment, migrate, refactor

Lastly, take advantage of this period of exploration while learning the language. Question existing patterns/conventions for writing Android apps and ask yourself if/how Kotlin might enable you to write alternative solutions.

— can your code be refactored to avoid null properties?

— can you replace helper classes/methods with top-level functions?

— can you avoid handling null properties by using a delegate?

— could a logical path be expressed using functional concepts to reduce a class’s state?

How can Kotlin help improve your product?


Confidence in adopting Kotlin moving forward

We are very comfortable with our investment into Kotlin for the foreseeable future.

The language and tooling continue to improve. Between JetBrains, Google, and the community, there is a great team behind improving these developer stories.

As documentation and samples continue to be added for the language, it will become increasingly less daunting to try Kotlin which we believe will increase adoption and make it an even more viable option going forward.

We look forward to further exploring Kotlin and everything it can do to help us build great education products for our users.


I love to meet/talk/discuss and help where I can. If you want to chat or ask a question you can follow me on Twitter, YouTube, Instagram and Facebook.

Check Out My YouTube Channel

My year with Udacity’s Android Nanodegree

Motivations and Starting Out

A little over a year ago, I had never heard of Udacity and had zero experience with MOOCs. I was a relatively new developer, having only completed my graduate work a year prior. I was eager to improve my skills both with the Android platform and as a software engineer in general.

Then, while live-streaming the Google I/O 2015 keynote I saw an announcement about this thing called a Nanodegree program as a means of learning about Android development using a curriculum and courses developed in conjunction with Google. I was immediately intrigued. It seemed like the perfect opportunity to level-up my skill-set, and I registered for a free trial that same week.

Continue reading