Skip to content

OOP in Kotlin and Android User Interface

Kotlin OOP

1 Classes

  • Packages: are namespaces that contain classes and other packages
package com.example.myapp // declare the package that this class belongs to
class Foo {}
class Foo // no body
val foo = Foo() // create an instance of Foo, just like calling a function
class Foo (var count: Int) { // primary constructor, short-hand for declaring a property
    fun increment() { count++ }
}
class Foo constructor(var count: Int) { // primary constructor, formal
    fun increment() { count++ }
}
  • If you have multiple multiple or private constructors, the constructor keyword in the class definition is required.
  • The init block is executed only once after the primary constructor is called.
class Foo constructor(stringCount: String, val name: String) {
    var count: Int = 0 // property
    init {
        println("Foo created with count $count") // only executed once, after primary constructor
        // only properties defined above can be accessed, including those defined in the primary constructor
        count = stringCount.toInt() // convert string to int
    }
    fun increment() { count++ }

    /**
      because `stringCount` does not have `val | var` keyword, it is not a property,
            and cannot be accessed outside of the primary constructor or init block.
        While `name` is a property, and can be accessed anywhere in the class.
     */
}
  • Multiple Constructors can be defined using the constructor keyword, and must call the primary constructor.
  • The primary constructor can not execute any code other than initializing properties, but using the init block, you can execute code after the primary constructor is called (without the need of secondary constructors).
class Foo constructor(var count: Int, val con: String = 'Primary Constructor' ) { // primary constructor, with default value
    init {
        println("Foo created with count $count by constructor $con") // only executed once, after primary constructor
    }
    constructor(stringCount: String) : this(stringCount.toInt(), 'Constructor 1') { // call primary constructor
        println("Foo created with count $count") // only executed once, after primary constructor
        // execute more code if needed
    }
    constructor() : this(0, 'Constructor 2') // call primary constructor
}

val foo1 = Foo(1) // call primary constructor
val foo2 = Foo("2") // call secondary constructor
val foo3 = Foo() // call secondary constructor

/**
1. Foo created with count 1 by constructor Primary Constructor
2. Foo created with count 2 by constructor Constructor 1
2. Foo created with count 2
3. Foo created with count 0 by constructor Constructor 2
*/
  • By default all classes extend Any, which is the equivalent of Object in Java.
  • Kotlin uses : to denote inheritance, and open keyword to denote a class that can be inherited from.
  • Classes Can not be inherited by default; a class must be declared open to be inherited from.
  • Any class methods Can NOT be overridden by default; a method must be declared open to be overridden.
  • The child class must use the override keyword to override an open method from the parent class.
  • An open function from a parent, is by default, open for all child classes to override, no matter how many levels of inheritance; unless a child class declares the function as final, then it is closed for child classes that inherit from it (upper levels can still override it).
class DefaultInherit: Any() { } // Foo extends Any, default no need to specify

open class Foo  constructor (val status: Int) { // open keyword to denote a class that can be inherited from
    open fun foo() { println("Foo") }
}
class Bar : Foo(1) { // Bar inherits from Foo, must call primary constructor passing in the required parameters
    fun bar() { println("Bar") }
    override fun foo() { println("Bar: overrides foo") } // override foo() method
}

val foo = Foo(1)
val bar = Bar()
foo.foo() // Foo
bar.foo() // Bar: overrides foo
bar.bar() // Bar
  • Property Overriding:
    • By default if the child has a property with the same name as the parent, will cause a compile error.
    • In this case, the child class must use the override keyword to override the property from the parent class.
  • You still have access to the parent properties or methods that overriden by the child class, by using the super keyword.
  • The is keyword is used to check if an object is an instance of a class or inherits from a class: if (obj is Foo) { ... }
  • Using is with when:
open class Animal {}
class Dog : Animal() {}
class Cat : Animal() {}

fun classifyAnimal(animal: Animal): Unit = when (animal) {
    is Dog -> println("animal is a Dog")
    is Cat -> println("animal is a Cat")
    else -> println("animal is neither a Dog nor a Cat")
}

fun main() {
    val dog = Dog()
    val cat = Cat()
    val animal: Animal =
        Animal() // animal is a Dog, but declared as Animal; we can not tell what it is at compile time

    classifyAnimal(dog) // animal is a Dog
    classifyAnimal(cat) // animal is a Cat
    classifyAnimal(animal) // animal is a Dog
}
  • Casting can be done using the as keyword: val dog = animal as Dog.
  • Kotlin has Smart Casting, which means that if you check if an object is an instance of a class, then you can use it as that class without casting it within the if block.
if (animal is Dog) {
    animal.bark() // no need to cast animal to Dog, Compiler knows that animal is a Dog `Smart Casting`
    // animal as Dog
}

// animal is an Animal, not a Dog

2 Properties

  • All properties are instance properties by default, meaning that they are associated with an instance of the class.
  • Properties can be initialized:
    • When declared.
    • In an init block.
    • Later in the class body, using a custom setter: lateinit keyword is used to denote that the property will be initialized later. The property must be initialized before it is used, otherwise it will throw an exception. lateinit can only be used with var properties, and only with non-nullable types and not primitive types.
    • by lazy keyword is used to denote that the property will be initialized later. Compiler will initialize lazy property Only one time when it is first accessed. by lazy is thread safe, and will only be initialized once, even if it is accessed by multiple threads. By lazy must provide lambda expression to initialize the property.
    • Fake properties: as functions may be called without parentheses, fake properties are used to distinguish between properties and functions.
    • Constants: using the const keyword, the property must be a top-level or an object property, and must be of type String or a primitive type. Constants are compiled into static fields, and can be accessed from Java code.
class Foo (
    val _whenDeclared: Int = 1, // initialized when declared
){
    val whenDeclared: Int = this._whenDeclared // initialized when declared
    val inInitBlock: Int // initialized later in init block
    lateinit var later: String // initialized later in class body

    init {
        inInitBlock = 1 // initialized in init block
    }

    val lazy: String by lazy { // initialized later in class body
        println("lazy initialized")
        "lazy"
    }

    val fakeProperty: String get() = "fakeProperty" // fake property

    companion object {
        const val CONSTANT = "CONSTANT" // constant
    }

    fun foo() {
        println("foo")
    }

}

3 Visibility

Visibility: public, private, protected, internal

  • Public: default visibility, visible everywhere.
  • Private: visible only within the class (not subclasses), and within the file (if top-level or used outside of classes).
  • Protected: visible within the class and subclasses.
  • Internal: visible within the module (project).

Scope: Global, Instance, Local

  • Global:
    • Top-level: declared outside of any class.
    • Immutable globals have lifetime of the application.
    • Mutable globals may get garbage collected.
  • Instance: declared within a class, but outside of any function.
  • Local: declared within a function.

4 Abstract Classes and Interfaces

Abstract Classes

  • Use the abstract keyword to denote an abstract class.
  • Can have concrete methods or abstract methods/properties.
  • Abstract classes are open by default, so they can be inherited from.
  • Subclasses must implement all abstract methods/properties, or be abstract themselves and must be implemented by their subclasses.

Interfaces

  • Use the interface keyword to denote an interface.
  • The rules of subclassing apply to interfaces (they are open by default, and must be implemented by their subclasses, can have both concrete or abstract methods).
  • A class can implement multiple interfaces, but can only inherit from one class.
  • Interfaces can NOT have concrete properties, only abstract properties.
  • Interfaces can not manage state, to manage state, use an abstract class.
  • Interfaces can not have constructors or protected members.
  • Interfaces can implement other interfaces, and provide implementations for their methods.

interface Aquatic {
    fun swim() // abstract method
    fun dive() { // concrete method
        println("diving")
    }
}

class Animal {
    fun eat() {
        println("eating")
    }
    abstract fun sleep() // abstract method
}

// interfaces do not have constructors, so they can not be instantiated and no need to use the `()` after the class name
abstract class Fish : Animal(), Aquatic { // abstract class that implements an interface, and inherits from a class
    abstract override fun swim() // abstract method, left for subclasses to implement
    override fun dive() { // concrete method, implemented.
        println("diving")
    }
}

class Shark : Fish() {
    override fun swim() { // concrete method
        println("swimming")
    }
}

class Dolphin : Fish() {
    override fun swim() { // concrete method
        println("swimming")
    }
}

5 Data Classes

  • A data class in Kotlin (similar to AutoValue in Java) defines an immutable class that has getters but no setters as well as methods:
    • equals(): compares the values of the properties.
    • hashCode(): generates a hash code based on the values of the properties.
    • toString(): generates a string representation of the object.
    • copy(): creates a copy of the object, and can be used to change some of the properties.
  • Use the data keyword to denote a data class.
  • Data classes Can NOT be open or abstract, so they can not be inherited from (extended)
data class Person(
    val name: String,
    val age: Int,
    val address: String
)

6 The object Keyword

Singleton

  • Using the object keyword, we can create a class and instantiate it at the same time.
  • Useful for creating singletons, or objects that are used only once.
  • Objects can not have constructors, and can not be instantiated using the () after the class name.
  • Objects can inherit from classes and implement interfaces.
object Singleton {
    fun foo() {
        println("foo")
    }
}

fun main() {
    Singleton.foo() // no need to instantiate the object
}

Companion Objects

  • Using the companion object keyword, we can create a class and instantiate it at the same time.
  • This is useful for creating static methods and properties within a class.
  • The main advantage is that the companion object can access the private members of the class.
class Foo {
    companion object {
        var instanceCount = 0
        accessPrivateMember(instance: Foo) {
            println("accessing private member: ${instance.privateMember}")
        }
    }

    companion object FooNamedCompanion { // named companion object
        var myNamedCompanionObjectCount = 0
    }

    init {
        Foo.instanceCount++
    }

    private val privateMember = "private member"
}

fun main() {
    val foo1 = Foo()
    val foo2 = Foo()
    val foo3 = Foo()
    println(Foo.instanceCount) // 3
    Foo.accessPrivateMember(foo1) // accessing private member: private member
    println(Foo.myNamedCompanionObjectCount) // 0, accessing the named companion object by the class name not the object name
}

Object Expressions

  • Using the object keyword, we can create a class and instantiate it at the same time (thus creating an anonymous class).
abstract class OnClickListener {
    abstract fun onClick(v: View)
}

class View {
    fun setOnClickListener(listener: OnClickListener) {
    // TODO something with this
    }
}

fun clickyClickyClicky(view: View) {
    view.setOnClickListener(object : OnClickListener() { // anonymous class, aka, object expression
        override fun onClick(v: View) {
        // TODO something useful
        }
    })
}

7 Nested Classes and Objects

  • Classes can have nested classes and objects.
  • Nested objects can be used to group constants and static methods (companion objects), or normal instance methods and properties (regular objects).
  • Nested Classes are static by default, so they can not access the members of the outer class, unless you instantiate them within the class, and then they become nested objects.
  • Inner classes are nested classes that are not static, so they can access the members of the outer class.
class Outer {
    inner class Inner { // inner class, can access the members of the outer class
        fun innerInner() { return this} // this refers to the inner class
        fun outerInner() { return this@Outer} // this@Outer refers to the outer class

    }

    class StaticNested { // static nested class, can not access the members of the outer class
           // this refers to the static nested class
           // can not access the members of the outer class
    }

    class Nested {} // static
    val nested = Nested() // nested class is now nested object, can access the members of the outer class

    object NestedObject {} // instance
    companion object NestedCompanionObject {} // static
}

fun main() {
    val outer = Outer()
    val myInner = outer.Inner()
    val staticNested = Outer.StaticNested()
    val nested = outer.nested
    val nestedObject = Outer.NestedObject
    val nestedCompanionObject = Outer.NestedCompanionObject
}

8 Enums and Sealed Classes

Enums

  • Enums are used to define a set of constants.
  • Enums are classes, so they can have properties and methods or passed as parameters to functions or other classes.
enum class Color {
    RED, GREEN, BLUE
}

enum class HttpResponseCode(val code: Int, val description: String) {
    OK(200, "OK"),
    NOT_FOUND(404, "Not Found"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error")

    toString() = "$code: $description"
}

fun main() {
    val ok = HttpResponseCode.OK
    println(ok.code) // 200
    println(ok.description) // OK
    println(ok) // 200: OK, implicitly calls toString()


    Color.values().forEach { println(it) } // RED, GREEN, BLUE

    val e404 = HttpResponseCode.valueOf("NOT_FOUND") // 404: Not Found, implicitly calls toString()
    val e404 = HttpResponseCode.NOT_FOUND
    e404.code // 404
    e404.description // Not Found
    e404.toString() // 404: Not Found
    e404.ordinal // 1, the index of the enum. available on all enums
    e404.name // NOT_FOUND, the name of the enum. available on all enums

}

Sealed Classes

  • Sealed classes are used to define a set of classes that are related to each other.
  • Used to control class hierarchies.
  • Sealed Classes are abstract by default, so they can not be instantiated, but they can be extended.
sealed class PossibleShapes {
    abstract fun info(): String
    data class Circle(val radius: Double)
    data class Rectangle(val width: Double, val height: Double)
    data class Triangle(val base: Double, val height: Double)
}

class Shape : PossibleShapes() {
    override fun info(): String {
        return when (this) { // exhaustive when statement, no need for else, all cases are strictly coming from PossibleShapes
            is Circle -> "Circle with radius $radius"
            is Rectangle -> "Rectangle with width $width and height $height"
            is Triangle -> "Triangle with base $base and height $height"
        }
    }
}

fun main() {
    val shape = Shape()
    val circle = shape.Circle(5.0)
    val rectangle = shape.Rectangle(5.0, 10.0)
    val triangle = shape.Triangle(5.0, 10.0)
    circle.info() // Circle with radius 5.0
    rectangle.info() // Rectangle with width 5.0 and height 10.0
    triangle.info() // Triangle with base 5.0 and height 10.0
}

9 11 Kotlin for Android Tutorial

  • Kotlin Android Development contains the following aspects:
    • TextView: TextVew, AutoCompleteTextView (subclass of EditText), CheckedTextView.
    • ScrollView: HorizontalScrollView, VerticalScrollView.
    • ImageView.
    • ListView.
    • Button: ImageButton, RadioButton.
    • EditText.
    • Layouts: LinearLayout, RelativeLayout, TableLayout, FrameLayout, GridLayout, ConstraintLayout.
    • Bar: SeekBar, RatingBar, ProgressBar.
    • Switcher: TextSwitcher, ImageSwitcher, ViewSwitcher.
    • TimeAndDatePicker: TimePicker, DatePicker.
    • Spinner.
    • TextClock.
    • Chronometer.
    • Notification.
    • Toast.
    • RadioGroup.
    • Slider.
    • WebView: simpl embedded browser within the app, see example
  • R is a class that contains all the resources of the application, that are generated by the compiler during the build process. It is available in every activity and fragment.
  • All resources can be declared in XML files, and accessed in Kotlin code using the R class; or dynamically created in Kotlin code (similar to document.createElement in JavaScript).

11 Android Development Tutorial

  • Open Project Structure from File menu, and set the Project SDK to the latest version of Android SDK.
  • Every Module in the app has its own build.gradle file.
  • Static java dependencies (jar files), can be placed on /app/libs folder, and added to the build.gradle files using the file -> Project Structure -> Dependencies -> + -> File Dependency menu.

10 12 More OOP

  • In 10, Classes, Objects, Nested Classes, Inner Classes, and Type Aliases were covered.
  • In 12, we have gone through different Object Oriented Concepts in Kotlin programming language.
  • In 13, Basic OOP concepts were covered.
  • In 14, A road map for all Kotlin provided by GeekForGeeks was covered. Refer to it when needed.
  • In 15, A guide for building a simple Android app was covered.

15 Building a Simple Android App

  • The AndroidManifest.xml is ready by Android Runtime while your app is executing.
  • Project Structure:
    • manifests: contains the AndroidManifest.xml file.
    • java:
      • com.example.myfirstapp: contains the MainActivity.kt file.
      • com.example.myfirstapp (androidTest): all e2e tests.
      • com.example.myfirstapp (test): all unit tests.
    • res:
      • drawable: contains all images.
      • layout: contains all layouts, which are XML files that define the UI.
      • menu: contains all menus.
      • mipmap: contains all icons, which are used in the launcher.
      • navigation: contains all navigation graphs, which are XML files that define the navigation between screens.
      • values: contains all strings, colors, and styles.
        • strings.xml: contains all strings.
        • colors.xml: contains all colors.
        • You can create your own XML files to define your own styles.
  • Avd Manager is used to create and manage virtual devices.

References


  1. Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 8: Basic Classes. 

  2. Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 10: Properties. 

  3. Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 11: Visibility. 

  4. Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 12: Abstract Classes and Interfaces. 

  5. Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 13: Data Classes. 

  6. Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 14: The object Keyword. 

  7. Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 15: Nested Classes and Objects. 

  8. Murphy, M. L. (2021). Elements of kotlin. CommonsWare. https://commonsware.com/Kotlin/Kotlin-FINAL.pdf Chapter 16: Enums and Sealed Classes. 

  9. GeeksforGeeks. (2021, August 17). Kotlin android tutorial. Retrieved June 23, 2022, from https://www.geeksforgeeks.org/kotlin-android-tutorial/ 

  10. Kotlin - class and objects. (n.d.). tutorialspoint. https://www.tutorialspoint.com/kotlin/kotlin_class_and_object.htm 

  11. Kotlin android tutorial. (2021, January 21). TutorialKart. https://www.tutorialkart.com/kotlin-android-tutorial/ 

  12. Kotlin object oriented programming concepts. (2021, January 21). TutorialKart. https://www.tutorialkart.com/kotlin/kotlin-oop/#gsc.tab=0 

  13. Kotlin OOP. (n.d.). W3Schools. https://www.w3schools.com/kotlin/kotlin_oop.php 

  14. Kotlin programming language. (2021, November 24). GeeksforGeeks. Retrieved June 23, 2022, from https://www.geeksforgeeks.org/kotlin-programming-language/ 

  15. Lmf. (2020, November 6). Build your first android app in kotlin. Android Developers. https://developer.android.com/codelabs/build-your-first-android-app-kotlin#0