Kotlin 🌈

Hero image for Kotlin 🌈

When it comes to Java, most conventions are shared with Kotlin. For generic conventions, the team follows Google contributor Guidelines and Java Guideline, Futurice’s Android Guideline and Ribot’s Android Guidelines.

Formatting

Indentation

  • Only use spaces, no tabs allowed.
  • Use 4 spaces indentation for blocks and 8 spaces indentation for line wraps.
  • It’s also flexible to change the rules, too, as there is a recommendation from Google to use 2 spaces for blocks and 4 spaces for line wraps. So, depending on the start of the project, the team should always follow the base structure.

Blank Line

  • There must be one blank line after the class declaration and another at the end of the file.

    class SomeClass {
    
      // Your code
    }
    
  • Separate each interface method using a blank line. No need to use a blank line after the interface name.

    interface Input {
      fun firstName(firstName: String)
    
      fun lastName(lastName: String)
    }
    

Line of Code

  • Line length: it shouldn’t exceed the screen width, ideally should be no longer than 100 characters.
  • Line amount per class: should be no longer than 600 lines per class.

Line Breaks

  • If the method declaration exceeds the maximum line length, use a line break before each parameter. For methods with more than two parameters, always use a line break before each one.

    fun updatePerson(firstName: String, middleName: String, lastName: String) {
      // Your code
    }
    
    fun updatePerson(
      firstName: String,
      middleName: String,
      lastName: String
    ) {
      // Your code
    }
    

    Method calls with more than two arguments are required to have line breaks along with named arguments.

    updatePerson(
      firstName = "John",
      middleName = "Bob",
      lastName = "Doe"
    )
    

Vertical Spacing

To ensure a good experience during code reviews, developers must maintain consistency in the alignment of their new lines of code with the existing codebase.

Trailing Commas

A trailing comma is a comma symbol placed after the last item of a series of elements:

  • Class properties and parameters:

    class Customer(
        val name: String,
        val lastName: String, // trailing comma
    )
    class Customer(
        val name: String,
        lastName: String, // trailing comma
    )
    
  • Function value parameters:

    fun powerOf(
        number: Int,
        exponent: Int, // trailing comma
    ) { /*...*/ }
    constructor(
        x: Comparable<Number>,
        y: Iterable<Number>, // trailing comma
    ) {}
    fun print(
        vararg quantity: Int,
        description: String, // trailing comma
    ) {}
    

Unlock the power of trailing commas and enjoy a host of benefits:

  • Cleaner version-control diffs that place all the focus on the changed value.
  • Effortless addition and reordering of elements without the need to add or delete commas.
  • Simplified code generation, such as for object initializers, where the last element can also utilize a trailing comma.

The Kotlin style guide encourages using trailing commas, so the team should consider it as optional since the code will still work without them. Therefore, when conducting a code review, kindly avoid asking to add it if it is missing, or refrain from removing it if it is present.

For IntelliJ IDEA IDE, adopt the trailing commas by enabling it in the Code Style settings: go to Settings/Preferences | Editor | Code Style | Kotlin, open the Other tab, and select the Use trailing comma option.

Others

  • Brace style:

    fun main()
    {
        // Not this
    }
    
    fun main() {
        // Your code
    }
    
  • Conditional statements are preferred to be enclosed with braces:

    if (conditionPassed)
        doSomething()
    
    if (conditionPassed) {
        doSomething()
    }
    if (someTest) { doSomethingElse() }
    
    if (someTest) doSomethingElse() // Still acceptable
    

    For if/else blocks where short one-line statements (< max line length) are possible, braces are not required. For example:

    val someObject = if (isSomethingTrue) thisObject else thatObject
    if (isSomethingTrue) doSomething() else doSomethingElse()
    
  • Method chain case:

    When multiple methods are chained in the same line * for example when using Builders * every call to a method should go in its own line, breaking the line before the .. For example:

    SomeClass.Builder.with(context)
            .setAlpha("1")
            .setCancelable(false)
            .setExtras(bundle)
            .build()
    
  • RxJava chaining style:

    fun syncLocations(): Observable<Location> {
        return locationHelper.getAllLocations()
              .concatMap { locationService.getLocation(it.id) }
              .retry { _, throwable ->
                  throwable is Error
              }
    }
    

Naming

Packages

Package names are all lower-case, multiple words concatenated together, without hyphens or underscores:

com.nimble.android_demo
com.nimble.androiddemo

Properties

  • Must be written in lowerCamelCase with descriptive names:
private var temp: Int = -1
private var a: Boolean = false
private var temporary: Int = -1
private var isValid: Boolean = false
  • Constants must be written in uppercase:
const val extra_value = 42
const val EXTRA_VALUE = 42
  • Acronyms should be treated as words:
XMLHTTPRequest
IOStream
URL: String
findPostByID
XmlHttpRequest
IoStream
url: String
findPostById
  • Example:
class ExampleClass {
    val publicFinalProperty: Int
    var publicProperty: Int = 0
    protected val protectedProperty: Int
    private val url: String

    private fun findRateById(): Int {
        // Your code
    }
}

const val SOME_CONSTANT = 42

Classes and Interfaces

  • Written in UpperCamelCase.

    BaseACTIVITY
    
    BaseActivity
    

Methods

  • Written in lowerCamelCase with descriptive names.

    fun setvalue(value: String)
    private fun check()
    
    fun setValue(value: String)
    private fun checkStyle() // or private fun validateStyle()
    

Declaration

Visibility Modifiers

  • Non-public visibility modifiers should be explicitly defined for classes, methods, and properties:

    var isPrivateProperty: Boolean = false
    public fun publicMethod() {
        // Nope
    }
    
    private var isPrivateProperty: Boolean = false
    fun publicMethod() {
        // Your code
    }
    

Classes

  • Should be one class per source file, although inner classes are still encouraged in some situations.

Constants

  • Should be declared at the top-level scope.

  • Do NOT extract a constant if it does not provide any extra information or insight into the value, and is not for reusability or maintenance simplification.

    const val ZERO = 0
    private const val ONE = 1
    const val EMPTY_STRING = ""
    
    class SomeClass {
      
      private fun scrollToPosition(index: Int) {
          if (index != ZERO) {
            // Your code
          }
      }
    }
    
    const val MAX_API_RETRY_ATTEMPTS = 3
    private const val TOKEN_EXPIRED_ERROR = "token_expired"
    private const val SCROLLING_THRESHOLD = 0
      
    class SomeClass {
    
      private fun scrollToPosition(index: Int) {
          if (index + SCROLLING_THRESHOLD >= lastIndex) {
            // Your code
          }
      }
    }
    

Class Member Ordering

  • Class members should be ordered in the following manner:
    1. private val/var
    2. overridden val with backing property
    3. overridden method
  • private methods should be placed close to the call sites. For example:

    class SomeClass : BaseClass() {
    
        private val form = BehaviorSubject.createDefault(Form())
        private var id = -1
    
        private val _enableButton = BehaviorSubject.createDefault(false)
        override val enableButton: Observable<Boolean>
            get() = _enableButton
    
        override fun firstName(firstName: String) {
            validateName(firstName)
        }
    
        private fun validateName(name: String) {
            // Your code
        }
    
        override fun someOtherMethod(something: String) {
            // Your code
        }
    }
    

Comments

Comments should follow the Kotlin documentation comment style guide.

  • For short documentation comments, use /** */:

    // This does a simple calculation
    fun simpleCalculation() { }
    
    // This is a simple class
    class SimpleClass { }
    
    /** This does a simple calculation */
    fun simpleCalculation() { }
    
    /** This is a simple class */
    class SimpleClass { }
    
  • For short inlined comments, use // sparingly:

    fun simpleCalculation() {
        
      /**
       * Multiple lines comment is not expected here
       * The X-coordinate position, starts from 0.0
       */
      val complexCoordinatesX = 0.0;
        
      val complexElevation = -5F; /** Elevation begins from -5 */
    }
    
    fun simpleCalculation() {
        
      // The X-coordinate position, it starts from 0.0
      val complexCoordinatesX = 0.0;
    
      val complexElevation = -5F; // Elevation begins from -5
    }
    
  • For longer documentation comments, place the opening /** on a separate line, begin each subsequent line with an asterisk, and ensure they are vertically aligned:

    // Two line
    // comments
    fun complexTask() { /*...*/ }
    
    /**
      * Block comment but mis-aligned
      */
    fun complexExecution() { /*...*/ }
    
    /**
     * Block comment
     * that is longer than 2 lines
     */
     fun complexTask() { /*...*/ }
    
  • Use Kotlin style function arguments and expected returns, do not use @param and @return tags:

    /**
     * Returns the absolute value of the given number.
     * @param number The number to return the absolute value for.
     * @return The absolute value.
     */
     fun abs(number: Int): Int { /*...*/ }
    
    /**
     * Returns the absolute value of the given [number].
     */
     fun abs(number: Int): Int { /*...*/ }
    

Reference Resources