Skip to content

Kotlin Basics

Inheritance

After a class is created, it can be instantiated. However, sometimes it is necessary to add additional functionality to a class. This can be done through class inheritance, and is one of the key requirements of command-based programming. The official Kotlin documentation may be more comprehensive, but this is a general overview of how it works:

open class Parent{ // the keyword 'open' specifies that this class can be extended
    var foo = 0
    open fun doSomething(){ // the keyword 'open' can be used before a function to allow it to be overridden by child classes
        // do stuff
    }
}

class Child: Parent(){ // the colon specifies that Child inherits from a new instance of class 'Parent', you may also use 'object' in place of 'class' in this case
    override fun doSomething(){ // this is what will now run if an instance of 'Child' has 'doSomething()' called
        super.foo++ // the keyword 'super' is used to access the parent class' data
        super.doSomething() // this allows you to run the function that exists in the parent class, so this will "do stuff"
    }
}

Constructors

Kotlin uses init blocks as part of constructors. These allow you to run functions or initialize variables upon class or object instantiation.

class ExampleClass {
    var foo = 0
    init { // this runs after foo is initialized, but before bar is initialized
        foo++
    }
    var bar = 0
    init{ // you may have multiple init blocks throughout a class
        foo++
        bar++
    }
    // when ExampleClass is instantiated, foo will be 2, and bar will be 1
}

Singletons

While "objects" typically refer to anything created with the class keyword, Kotlin has its own special object keyword.

An object, while functionally similar to a class, is limited to one singular instance. This is known as a "singleton."

Singletons are especially useful in robotics, as they provide clean organization for subsystems without having to pass a singular instance around everywhere. Instead, you simply import the object and reference it as if it were a typical instance of a class.

TODO: Give example of how they work and are different from classes.

Scope Functions

Read the Kotlin docs for comprehensive information. The one you will probably use most often is .apply{}.

.apply{} runs methods on an object, but it removes the need to repeatedly reference the object. Instead of someObject.methodOne() and then someObject.methodTwo(), you can do someObject.apply{methodOne(); methodTwo()}, removing redundant references to the object.

For example, you can use this when creating motors:

val motor = TalonFX(1).apply{
    outputInverted = true
    otherThing = false
    someNumber = 2.0
}
// The above lines are functionally the same as:
val motor = TalonFX(1)
motor.outputInverted = true
motor.otherThing = false
motor.someNumber = 2.0
These just make some things simpler.

Lambdas

Oftentimes, you will want to pass a function as a parameter to another function. This is typically part of WPILib's command-based framework, but you may also use it in other places. Lambdas are essentially anonymous functions that can be passed around and run wherever needed.

InstantCommand() is a good example of this:

InstantCommand({Subsystem.doSomething()})
Here, the lambda {Subsystem.doSomething()} is passed to InstantCommand(), which will run it once when the command is scheduled. Lambdas can also take parameters, for example:

Sometimes, WPILib may require a "Consumer," which is a lambda that takes parameters but does not return anything. For example:

SomeWPILibClass(){number: Int -> println("The number is $number")})
Here, the lambda takes an Int parameter called number, and prints it when run.

Note

You can remove parenthesis in Kotlin if the lambda is the last parameter in a function call, so the above can also be written as:

SomeWPILibClass{number: Int -> println("The number is $number")}

Null Protection

Kotlin has built-in null protection, which prevents null pointer exceptions. This is done through the use of ? and !!. A variable that may be null is declared with a ? after its type, for example:

var maybeNull: String? = null // this variable may be null
var notNull: String = "Hello" // this variable may not be null
If you try to access a variable that may be null, you must use ? or !! to indicate how you want to handle the potential null value. Using ? will safely access the variable, returning null if it is null:
println(maybeNull?.length) // prints null
Using !! will throw a null pointer exception if the variable is null:
println(maybeNull!!.length) // throws a NullPointerException
There is also an operator called the Elvis operator ?: (because it looks like sideways elvis), which allows you to provide a default value if a variable is null:
println(maybeNull?.length ?: 0) // prints 0

Small Notes

  • Semicolons aren't needed in Kotlin, but they can be used just like in Java and other C-based languages if needed. Here are some use cases:
  • Inline statements, e.g. {doOneThing(); doAnotherThing()}
  • Adding functionality to enum classes, e.g.
    enum class State(val position: Double){
      UP(1.0),
      DOWN(0.0)
      ; // The semicolon separates enum values from additional class members
      // This specifies that each value of State is declared, opening the rest of the body to functions
      fun isPastHalfwayUp(): Boolean{ // Simple example function
          return position > 0.5
      }
    }
    // Now you can use this function elsewhere
    State.UP.isPastHalfwayUp() // returns true
    
  • Kotlin can read and use Java files. You may import them and use them like a normal Kotlin file if needed.