Skip to content
A style guide for Android developers writing in Kotlin
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
LICENSE
README.md

README.md

Kotlin Style Guide

A style guide for Android developers writing in Kotlin. These guidelines are based on the official Kotlin coding conventions.

Whitespace/Coding Style

Use ktlint to enforce styles. Apply ktlint to your IDE with ktlint --apply-to-idea-project --android, add a precommit hook with ktlint --install-git-pre-commit-hook and never think about whitespace again.

Naming Style

If in doubt, default to the Java Coding Conventions such as:

  • use of camelCase for names (and avoid underscore in names*)
  • types start with upper case
  • methods and properties start with lower case
  • use 4 space indentation (not tabs - Sorry Richard)
  • public functions should have documentation such that it appears in Kotlin Doc

*The exception to this is synthetic properties generated by the kotlin-android-extensions plugin. This creates snake_case property names from the snake_case View IDs defined in a layout file. To fix this you would either need to go against Android's layout ID naming conventions or create new objects which reference the synthetic properties. Hopefully in the future, JetBrains will release an updated plugin which strips out underscores from the generated properties.

Use camelCase names for View IDs. ConstraintLayout generates them, so clearly Google have changed their guidelines for XML.

const properties defined in a companion object should be SCREAMING_SNAKE_CASE as if you were defining a static final variable in Java.

Colons

There should be no space before a colon when separating a property/parameter name and it's type. There should however be a space before a colon which separates a type and supertype or interface:

interface Foo<out T : Any> : Bar {
    fun foo(a: Int): T
}

Lambdas

Follow the coding conventions:

In lambda expressions, spaces should be used around the curly braces, as well as around the arrow which separates the parameters from the body. Whenever possible, a lambda should be passed outside of parentheses.

list.filter { it > 10 }.map { element -> element * 2 }

In lambdas which are short and not nested, it's recommended to use the it convention instead of declaring the parameter explicitly. In nested lambdas with parameters, parameters should be always declared explicitly.

To add to this, lambda parameters (or those in destructured declarations) which are unused should be replaced with an underscore, unless leaving the parameter significantly improves readability. Android Studio will nag you about this anyway:

 .subscribe(
	{ bitmap -> view.displayImage(bitmap) },
	{ _ -> view.setUiState(UiState.FAILURE) })

Lambda Returns

Adding a return to a Lambda in Kotlin is optional, and multiline Lambdas will just return the last line. In complex multi-line Lambdas, strongly consider adding a return@map/flatmap/whatever statement for the sake of clarity, as the return may not be particularly obvious at first glance.

Classes

Constructors

Initialize the properties of a class via primary constructor parameters instead of using an init block.

Do:

class Person(val firstName: String, val lastName: String, var age: Int) {
    // ...
}

Don't:

class Person(firstName: String, lastName: String, age: Int) {
    val firstName: String
    val lastName: String
    var age: Int

    init {
        this.firstName = firstName
        this.lastName = lastName
        this.age = age
    }

    // ...
}

For formatting these primary constructors, follow the Kotlin coding conventions:

Classes with longer headers should be formatted so that each primary constructor argument is in a separate line with indentation. Also, the closing parenthesis should be on a new line. If we use inheritance, then the superclass constructor call or list of implemented interfaces should be located on the same line as the parenthesis.

For multiple interfaces, the superclass constructor call should be located first and then each interface should be located in a different line:

class Person(
    id: Int,
    name: String,
    surname: String
) : Human(id, name),
    KotlinMaker {
    // ...
}

Companion Objects

Companion objects, such as those for Fragment newInstance methods, should be defined at the bottom of a class declaration;

class MyFragment : Fragment() {

    init {
        Injector.INSTANCE.inject(this)
    }

    @Inject
    lateinit var dataManager: DataManager

    fun doSomething() {
        // ...
    }

    companion object {
        const val BUNDLE_VALUE_ONE = "bundle_value_one"

        fun newInstance(value1: String, value2: String): MyFragment {
            // ...
        }
    }
}

As a sidenote regarding val properties in companion objects; accessing them from Java requires accessing the companion object too, which is ugly and not idiomatic:

String key = MyFragment.Companion.BUNDLE_VALUE_ONE;

Delcaring the property as a const val allows you to access a property as if it was static from Java code:

String key = MyFragment.BUNDLE_VALUE_ONE;

This also inlines any access to the val. There is a caveat though: this only works with primitives and Strings. For more information, check out this excellent article regarding constants in Kotlin.

Functions

Unit (void) Functions

If a function returns Unit (eg void in Java), the return type should be omitted:

fun foo() { // ": Unit" is omitted here
	// ...
}

The exception is an empty or stub method, in which case convert the method to an expression body:

fun foo() = Unit

With that being said, if stubbing functions prefer using Kotlin's inline TODO() function instead to make it obvious that a function is yet to be implemented:

fun foo() {
    TODO("This is yet to be implemented")
}

Expression Bodies

Functions whose bodies are single line should be converted to expression bodies where possible, unless that function returns Unit. One exception is passthrough methods, where the caller function is delegating to another class' method.

Whilst expression bodies allow the omission of the return type, strongly consider keeping it for the sake of readability unless the function quite obviously returns a primitive type such as a Boolean or String. Whilst it's trivial to work out the return type using an IDE, omitting these types make code review painful and more error prone.

Functions vs Properties

You'll often find that the Convert Java file to Kotlin intention in Android Studio/IntelliJ will convert some methods into properties. This might be a bit jarring coming from a Java background. However, properties are the idiomatic way of doing many things in Kotlin (no getters/setters, for instance). Follow JetBrain's simple guidelines to see whether a property is appropriate:

In some cases functions with no arguments might be interchangeable with read-only properties. Although the semantics are similar, there are some stylistic conventions on when to prefer one to another.

Prefer a property over a function when the underlying algorithm:

  • does not throw
  • has a O(1) complexity
  • is cheap to calculate (or caсhed on the first run)
  • returns the same result over invocations

Extension Functions

As you would with creating Java util classes, create an extension package and make separate files for each type:

  • extensions
    • ContextExtensions.kt
    • ViewExtensions.kt
    • ...

Extension functions are fun, but don't go overboard. Try not to hide mountains of complexity behind clever extensions.

Flow Control

When Statements

To keep when statements clean, do not call more than one function from a condition. Prefer to move these functions into a single enclosing function:

Do

when (aValue) {
    1 -> doSomethingForCaseOne()
    2 -> doSomethingForCaseTwo()
    3 -> doSomethingForCaseThree()
}

fun doSomethingForCaseTwo() {
    foo()
    bar()
}

Don't

when (aValue) {
    1 -> doSomethingForCaseOne()
    2 -> {
        foo()
        bar()
    }
    3 -> doSomethingForCaseThree()
}

Similarly, because when doesn't fall through, separate cases using commas if you wish for multiple cases to be handled the same way:

Do

when (aValue) {
    1, 3 -> doSomethingForCaseOneAndThree()
    2 -> doSomethingForCaseTwo()
}

Don't

when (aValue) {
    1 -> doSomethingForCaseOneAndThree()
    2 -> doSomethingForCaseTwo()
    3 -> doSomethingForCaseOneAndThree()
}

Imports

Do not use star imports. Disable these in Android Studio. If you've applied ktlint, it will have done this for you.

You can’t perform that action at this time.