Kotlin Notes

KotlinConf 2019

Wed 4th - Fri 16th Dec 2019

Table of Contents

  1. To Do
  2. Presentation Ideas
  3. Refactoring to Kotlin
    1. Part 1
      1. Presenter.java
      2. Session.java
      3. SessionTests.java
    2. Part 2
      1. Sessions.java
    3. Part 3
      1. JsonFormat.java
      2. Json.java
    4. Misc
  4. Building PWAs in Kotlin
    1. JS Dependencies
    2. Dukat
    3. Conclusion
    4. Questions
  5. Coroutines! Gotta catch em all!
    1. The basics
    2. Cancellation
    3. Failure
  6. What the f(p) is Kotlin?
    1. Create a Plan
    2. Pursuade with Data
    3. Deconstructing myths
    4. Can’t Stop, Won’t Stop
  7. Your first server with Ktor
  8. Firefox for Android
    1. Why
    2. Components
    3. Useful Kotlin features
  9. I walk the line
    1. Kotlin/Native
    2. Porch Pirate Protector
      1. Sharing information
    3. Libraries
    4. Summary
    5. Q & A
  10. Failure is not an option, Error handling strategies for Koktlin programs
    1. Java
    2. Kotlin
    3. Exceptions
    4. Avoid Errors
    5. Null to represent errors
    6. Move errors to the outer layers
    7. Algebraic data types
      1. Error reason representation
    8. Design your systems to be robust to errors
    9. Overall recommendation
  11. Creating full-stack web apps with Kotlin DSLs
    1. Project Intro
    2. Project Philosophy
      1. Lambdas with receivers
    3. Server with Ktor
    4. Data with Exposed
    5. Frontend with kotlinx.html
  12. The power of types
    1. Types
    2. Primitive Obsession
    3. Costs
      1. Garbage Collection
    4. Conclusion

To Do

Presentation Ideas

Refactoring to Kotlin

By Duncan and Nat

GitHub.

Tools > Kotlin > Configure Project in Kotlin.

Tools > Kotlin > Configure Kotlin Plugin Updates > turn on new converter. (This was later disabled since it didn’t work as well.)

When converting a project, just start with where active development is. Functional code in Java is verbose and it gets much better in Kotlin. Java/Kotlin interop can sometimes be messy, so try to avoid creating a messy interface.

Kotlin has a separate reflection library to Java (it is optional). Ideally with Kotlin you’ll have less reflection. There are gotchas reflecting into Kotlin classes from Java. You can fine tune (with annotations) how the Kotlin compiler generates Java bytecode.

Part 1

Presenter.java

Kotlin doesn’t have direct access to fields (it does have the automatic generation of getters/setters). Therefore Java classes that use your newly converted Kotlin class will need to have .field turned into .getField() (if the original class had a public field).

Kotlin has named parameters. For this reason you should keep the same parameter names when overriding methods.

You only need a { } if there’s anything inside it (except for try/catch), so you can leave them out if empty.

data class Presenter(val name: String)

Stuff with immutables and deep equality (functional stuff) is much better in Kotlin (so the data/domain layer is a good place to start).

Lombok and Kotlin don’t play nice together :-(.

Session.java

this.presenters = Collections.unmodifiableList(new ArrayList<>(presenters));

This is doing a defensive copy of the parameter (in Java).

init blocks are run as part of the constructor in every class.

Collection interop is “clever”. List is a Kotlin type and the compiler maps them onto Java collections. A Kotlin List is a view of a list that doesn’t let you modify it - standard Kotlin functions will give you immutable lists.

So in a pure Kotlin world, we could get rid of the snippet above, but unfortunately when Java uses the list, it doesn’t see the immutability (so the defensive copy is still useful).

asList will give you a view into the underlying data while toList will create a copy.

fun withTitle(newTitle: String): Session {
    return Session(newTitle, subtitle, slots, presenters)
}

But since we’re using a data class, we can use copy (and named parameters).

fun withTitle(newTitle: String): Session {
    return copy(title = newTitle)
}

And then:

fun withTitle(newTitle: String) = copy(title = newTitle)

This is a single expression style function. The return type is inferred, but it’s worth including if it’s non-obvious or nullable.

But for this method specifically, once we no longer have Java clients we’d get rid of them and let the clients use copy themselves. (It’s messier in Java due to lack of named parameters).

Interop can be painful if you use a lot of Optional in Java.

The void return type is called Unit. In Java there’s a difference between Callable and Runnable because of returning void. Kotlin deals with this much more nicely.

SessionTests.java

Kotlin has internal instead of package. Kotlin still has packages, but they’re purely for avoiding naming collisions, not visibility. (In the bytecode it becomes public with some metadata.)

@Test
fun `can change parameters`() {
    // ...
}

(You probably shouldn’t do this in production code, just in tests.)

If you’re not returning anything, you can leave : Unit out.

fun Session.withPresenters(vararg newLineUp: Presenter) =
    copy(presenters = newLineUp.toList())

This is an extension method, it can be called on objects of type Session. There is an implicit this in the body - which may be null. You can place this in the test file instead of the implementation file. The extension method is chosen based on the compile time type (there’s no runtime polymorphism). In IntelliJ, extenion functions are in itallics.

Part 2

Sessions.java

Kotlin objects are singletons. You normally don’t need them because you can have top level definitions for functions of variables. One case you may want to use them is when you want polymorphism.

?. is the same as ?: except the right hand side is null.

Kotlin has stream support built in.

Kotlin has an inline keyword which effects semantics (eg, a quick return from a lambda expression). You can write your own control structure. (You’ll need to recompile code to pick up new dependencies if they’re inline.) It can avoid boxing. Extension functions and inline means you don’t need macros in the language.

sessions.firstOrNull { (title) -> title.equals(exp) }
sessions.firstOrNull { it.title.equals(exp) }

The (title) is destructuring the it passed into the lambda and taking the first member of the data class. You probably should only do this for Pairs.

typealias SessionList = List<Session>

fun SessionList.findWithTitle(title: String): Session? =
    firstOrNull { it.title.equals(exp) }

Part 3

JsonFormat.java

If you have Class A that uses static methods on Class B, it’s better to convert Class A first, because otherwise it will be changed when Class B’s static methods are changed to instance methods on the object.

All Kotlin exceptions are unchecked.

Kotlin says that if the last parameter to a function is a function type and you’re using a lambda, you can put that lambda outside of the brackets.

The names of extension functions can be much shorter because they work in some context. Extension functions also make working with null nicer - you can use thing?.doThing as opposed to having your separate doThing function have to deal with taking a nullable parameter.

val text = node.asText()

Where node is a Java class. text will be typed as String, not String? and the compiler will insert a check to make sure that text is not null.

The Kotlin compiler will insert null checks on non-null parameters to public methods (but this can be turned off).

Json.java

You can define a function with syntax like (Presenter) -> JsonNode.

You can create infix functions with the infix keyword. They can still be called as a normal method.

The apply and let extension functions make it easier to turn everything into expressions.

You can use the elvis operator to throw:

val a = b!!
val c = d ?: throw ValueMissingException()

Misc

Kotlin will auto-magically deal with the differences between primitives and reference types. Int will normally be a primitive integer and will remain as such if it’s passed to an inline generic function. It will get turned into an object if it has object methods called on it (eg, toString), or if it is Nullable.

fun fred() {
    10.let {
        println(it)
    }
}

10 would be kept as a primitive.

val things = intArrayOf(1, 2, 3)

things.forEach {
    println(it)
}
val things = intListOf(1, 2, 3)
things.reduce { x, y -> x + y}

Neither of these box the integers (because the methods are inline). (This breaks if you call something like toList.)

Kotlin’s support for function composition and syntax for blocks means you need less/lighter frameworks which helps performance/start up time. (Compared to Spring/Java EE.)

A few gotchas in the Kotlin standard library - there’s a toString method on Any? which prints “null” on null.

println(null?.toString())
println(null.toString())

This can be confusing when searching through logs.

In Java you can write functions overloaded on primitive and the boxed value. These are hard to use from Kotlin.

Sequences are lazy versions of Iterables.

Benefits of Kotlin: Structural benefits - it encourages thinking in a functional and immutable way. Predictability - Java teams spend a lot of time debugging in prod (NPEs). They don’t have to track down where null references come from - this is built into the type checker and the design. Anecdotally there’s greater trust put in the teams using Kotlin (Nat).

Java with lambdas, immutable data, functional style is very amenable to converting to Kotlin. Because of the easiness of using immutable data structures, you get rid of the problem of aliasing (someone else from modifying something underneath you).

It’s fairly easy to get Java programmers to write Kotlin - approx a week of pair programming to get them up to decent (Nat).

For error handling, find an Either type. Reserve exceptions for things when you’re fundamentally broken. Try to lift the potential for errors out of the logic.

The Kotlin converter and compiler understand @NonNull and will help them produce better code.

Building PWAs in Kotlin

Erik Hellman. Slides.

Uses kotlinx.html.

Kotlin JS can work nicely with React, npm("@jetbrains/kotlin-react")) and npm("@jetbrains/kotlin-react-dom")).

npm install -g create-react-kotlin-app
npx create-react-kotlin-app demo

With this approach you’re not using Gradle.

JS Dependencies

How do you add dependencies on JS libraries?

kotlin {
    target {
        nodejs()
        browser()
    }

    sourceSets["main"].dependencies {
        implementation(kotlin("stdlib-js"))
        implementation(npm("jszip", "3.2.2"))
    }
}

Theny ou have to declare the API.

external class ZipObject {
    fun asnc(type: String): Promise<Any ?>
}

This will work nicely with coroutines and such. You may need to use the dynamic type for JavaScript types that can’t be represented in Kotlin.

Dukat

Requires the .d.ts files for a library. It will generate a Kotlin interface for that library. Union types in function parameters will be turned into overloaded functions.

It is experimental.

Conclusion

Is Kotlin/JS ready for production use? “It depends…”

The build system will build a single JS file (so you’ll need to do fun things to output a service worker, eg multiple modules). JS output can be very big.

Questions

The new Kotlin/JS build system wraps WebPack and you can pass in configuration options.

What’s the difference between Any and dynamic? You can try to use Any and if that doesn’t work, use dynamic.

Is there a place to find supported 3rd party libraries? kotlinlang.org has some stuff, but it’s somewhat out of date.

Can you talk a bit more about the size of the output JS? Not much data, but adding coroutines is normally the problem.

Is there a way to go from Kotlin to Typescript? Yes, you can publish Kotlin/JS npm packages, although unsure about whether you can do TypeScript type definitions.

Coroutines! Gotta catch em all!

By Florina Muntenescu.

About cancellation and exceptions of coroutines.

The basics

CoroutineScope helps you keep track of them, cancel them and be informed of failure.

val scope = CoroutineScope(Job())
val job = scope.launch {

}

A Job is a handle to a co and provides the lifecycle. Cos can be active, completing or finished, cancelling or cancelled. Finished means all of its children are also complete.

Cancellation

A co inherits the context from its parent.

New -> Active
Active -> Completing
Completing -> Complete [label="children finished"]
Active -> Cancelling [label="cancel / failure"]
Cancelling -> Cancelled [label="children cancelled"]
val scope = CoroutineScope(parentJob)

val job1 = scope.launch {}
val job2 = scope.launch{}

scope.cancel()

This will cancel all of its children.

A cancelled child doesn’t effect its syblings. Cancellation of coroutine code needs to be cooperative. You need to use job.isActive or ensureActive (throws an exception if not active). yield will also throw a CancellationException if the Job is cancelled. delay has this built in.

If you call cancel before join the state will be cancelled (but everything works fine), if you call join first it will be completed. You use await when you need the result of the co. If you call cancel before await you’ll get a JobCancelledException on the await.

val job = launch {
    try {
        work()
    } catch /* or finally */ {
        println("Performing cleanup")
        withContext(NonCancellable) {
            // Perform stuff here that you want
            // to be immune to cancel.
            // Eg, you couldn't call delay in the
            // parent scope because it would
            // break out of execution.
        }
    }
}

TL;DR

Failure

When a co throws an exception.

If a child fails, the parent will cancel all of its other children and then itself. A cancelled scope cannot start more coroutines. Eg, if you’re in a UI related scope, if one child throws it will kill all the other parts of the UI.

There is also the SupervisorJob where failure or cancellation of a child will not effect other children. This means that the exception propagates up to the parent. It only works for the immediate children (not grandchildren).

What the f(p) is Kotlin?

By Shelby Cohen and Katie Levy.

A talk about Intuit converting to Kotlin. One of them worked on TurboTax & and Android part, now on the backend servers. One’s on the developer logistics side of stuff.

Intuit has 9,000 employees, has 50 million customers.

Kotlin was introduced in 2018 and now it’s used all over the place.

Steps (for change): 1) Find your passion 2) Socialize (talk to eng leaders) 3) Proof of Concept (that addresses people’s concerns) 4) Create a plan (with timelines) 5) Pursuade with data 6) Deconstruct myths 7) Can’t Stop, Won’t Stop 8) Connect with a larger community

They started with converting data classes and created documentation along the way. Convinced cynical Java-lovers with presentations on new features, group coding and group training.

Useful Kotlin features:

They chatted with eng leaders to get a list of hesistations and then created a PoC to deal with that. This included data classes, functional programming.

Create a Plan

In their plan they changed (in order): 1) Test classes. 2) Data classes. 3) New features. 4) Then measured the impact.

Pursuade with Data

Deconstructing myths

Wrote a technical whitepaper that explained what problems they were solving. They made an external whitepaper.

They also used Arrow, a “functional companion to Kotlin’s Standard Library” eg, Option, Try, Either, IO, Applicative, Monad. (How big is Arrow?) map and flatMap operate on the right hand side of Either. Either can be used instead of throwing exceptions. You define the success state at “the lowest layer of the code”.

Key benefits from Arrow:

1) Status checks at lowest layer. 2) Handle errors with types, not exceptions. 3) Avoid mutation.

Can’t Stop, Won’t Stop

They started a weekly lunch learning community to talk and learn about Kotlin (eg, live coding, code reviews, reading Kotlin in Action).

Your first server with Ktor

By David, Big Nerd Ranch.

Ktor is a Kotlin-based framework to build and consume HTTP-based web services.

You need to have the Ktor plugin (by JetBrains) installed. This will set the project up with the required dependencies.

Ktor has a HTML DSL (and a CSS DSL).

An Application is the primary building block of a Ktor web service. It is configured in application.conf (written in HOCON) and you can add modules in Application.kt. A module is represented by a static method in the Application.kt. This file contains a main function and some modules.

@Suppress("unused")  // Used in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
    routing {
        get("/") {
            call.respondText(
                "Hello world",
                contentType = ContentType.Text.Plain
            )
        }
    }
}

To return something more fancy, use the HTML DSL.

get("/characters") {
    call.respondHtml {
        body {
            h1 { + "Characters" }
            ul {
                for (n in 1..10) {
                    li { + "Character $n: ${Character()}" }
                }
            }
        }
    }
}

Manual and names file

Firefox for Android

Why

The application is almost 10 years old. Then they built Firefox Focus. Later a different leam developed Firefox Rocket (became Firefox Lite). Then they created Firefox for Fire TV and then Firefox for Echo Show.

Goals:

Solution:

They are written from 100% Kotlin from the beginning. (But with some Rust for cross-platform things.)

Components

The BrowserEngine has a network stack and takes JS, HTML, CSS and displays them. On Android, the most obvious choice is WebView. Mozilla has GeckoView which is distributed as a library. (There is also Servo, written in Rust.)

GeckoView advantages:

Another component is the browser Toolbar.

FeatureComponents know how to connect other components together.

Other features required (each gets a component):

Useful Kotlin features

Explicit nullability.

Handling State - they’re using a Redux style thing for state. They have a data class BrowserState which is a tree. The components observe parts of the state.

The components dispatch a sealed class that goes to a reducer (they use a when clause and the compiler makes sure they don’t miss a class).

This was made more readable with type aliases.

I walk the line

A talk about what to conver tto Kotlin and what not to. The context is using Kotlin for multi-platform.

AirBnB talked about their experience with React Native - they found some should-be trivial things were horrendous and it was hard to track down the cause. You need at least 2 people to debug every problem (eg an Android developer/a JS developer/someone who understands the framework).

When building something cross platform, you can’t forget about developer experience (they’ll leave!).

Kotlin/Native

In this case, it’s more likely that the Android developer would know Kotlin.

Android and iOS have different designs and paradigms about user interaction so write once/run everywhere creates bad apps. Kotlin/Native is focused on your business logic, not the presentation.

Porch Pirate Protector

A locked box that you can remotely lock and unlock. They used Kotlin/Native for the:

MVA - Model-View-Anything Else. They used Model-View-Presenter (used by the 2018 kotlinconf-app).

Model: data class, View: interface, Presenter: class. Having the view being an interface is good for multiplatform, Fragments on Android, UIViewController on iOS, mocked for testing.

Kotlin/Native uses something like automatic reference counting for GC.

MVP became MVSP - Model-ViewState-Presenter. A View isn’t a live object, it’s a single object that is returned from the presenter. It became another data class. Data went from View Controller to the Presenter which returned more state to the View. This is unidirectional data flow (it makes Views simple). This suits SwiftUI.

Sharing information

They wanted to share style info between platforms. This doesn’t really work since colours are in XML in Android and assets on iOS. They ended up generating the XML/assets from a common source.

Libraries

ktor and kotlinx.serialisation work together really well, combining networking and parsing. Just annotate something with @Serializable.

(Concurrency on Kotlin/Native can be complicated, there’s a talk about it.)

On Android you can use LiveData along with Coroutines and Flow.

To figure out which parts of your app should be Kotlin Native, consider why are you using Kotlin/Native in the first place? You can’t totally ignore the platform just because you’re using a cross platform framework.

Summary

Q & A

How can you sell this to an iOS developer? You just add a build script run phase in XCode (it’s just like bringing in a library). It lets the iOS developers focus on the iOS specific parts of the application (not the business logic).

You mentioned that Kotlin/Native was better for your business logic over UI? I don’t think it was designed that way, but it is well suited for that. JetBrains are really good for tooling and design, but don’t deal with the UI parts of the frameworks.

Can we marry Flutter and Kotlin/Native? At AppDevCon someone did a talk about this - it was difficult. UI should be platform specific, business logic can be cross platform.

What’s your experience with Kotlin/Native and platform capabilities like Bluetooth or Location? Don’t have much experience. iOS and Android have very different systems for location.

Can Kotlin/Native work with C++? They don’t know.

How mature is the build system? With 1.3 they released a good tutorial for this.

Failure is not an option, Error handling strategies for Koktlin programs

Nat Pryce & Duncan McGregor

Kotlin is a bit conflicted in how you deal with failures.

Causes of failure:

Many crashes are caused by the incorrect handling of a recoverable failure. We may not take into account that an operation can fail. Knowledge of how to fix an error may be far from where the error occurs.

Java

Java tried to help with checked exceptions:

But people didn’t like this. RuntimeException became a subclass of Exception. It wasn’t applied consistently. Popular frameworks wrapped checked exceptions in runtime exceptions.

Higher order functions made checked exceptions more complicated.

Kotlin

Kotlin has no checked exceptions (they’re a Java feature, not a JVM feature). But we lose our ability to type check our error handling code.

What’s the best way to handle errors in Kotlin? It depends… The best way depends on performance constraints, team preferences, surrounding architecture (and these things may change during the life of the project).

Exceptions

They’re easy to throw, but a bit verbose to catch. We tend to translate errors as they cross domain boundaries.

But without support of the type checker we don’t know if we’ve caught all types of exception. Fuzzing can help you check you have coverage here (presenter library: snodge ).

Exceptions are fine when the behaviour of the program does not depend on the type of the error, eg:

(Although do use JVM exceptions where appropriate, eg when an index is out of bounds, illegal argument, since they have useful context.)

Avoid Errors

The problem with errors is that they can contaminate - eg if my data has an error parsing, what do we do with the entire request?

We could write functions that can’t fail.

fun readFrom(uri: String): ByteArray? {

}

This is a partial function that’s only valid over a certain range of inputs.

fun readFrom(uri: URI): ByteArray? {

}

Now it’s a total function (since uri parsing can’t fail). This lifts the error handling up.

Null to represent errors

Null is in the language, Kotlin’s type checker will keep you honest and there are nice language feature to deal with nulls. The downside is that there’s no information conveyed with the error, and we’re using the same construct for representing errors and absence of a value. It’s good for small, very local errors.

Using nulls is fine when:

Move errors to the outer layers

Pull IO up into upper layers, make dealing with the data total functions.

fun process(src: URI, dest: File) {
    val things = readFrom(src)
    process(things, dest)
}

fun process(src: URI, dest: File) {
    val things = readFrom(src)
    dest.writeLines(process(things))
}

Here we’ve moved opening dest out of process.

Algebraic data types

In Kotlin, this is a sealed class hierarchy.

sealed class Result<out T, out E>
data class Success<out T>(val value: T): Result<T, Nothing>()
data class Failure<out T>(val reason: E): Result<Nothing, E>()

This forces the programmer to consider the possibility of failure when you try to get the value.

when (result) {
    is Success<Value> -> doThing(result.value)
    is Failure<Error> -> doOtherThing(result.reason)
}

We can then defined extension functions on the class hierarchy (eg flatMap, mapSuccess and mapFailure) that makes working with these more pretty.

This can get ugly if a stage of the pipeline requires results from an earlier stage (other than the previous). The Arrow library can help with this.

Error reason representation

Results are fine when:

Design your systems to be robust to errors

Eg, transactions to prevent inconsistent state. Avoid mutating shared state. Queues and retries.

Differentiate between environmental failures (it’s worth retrying) and logic failures (you’ll get the same mistake).

Overall recommendation

Creating full-stack web apps with Kotlin DSLs

Pamela Hill

Project Intro

A Kotlin full-stack web app. It’s a vision board called Life Visualizer. A vision board is a collage of images that inspire you. You put in a goal and it uses Unsplash to find images for that goal.

A Webapp written in Kotlin/JS that uses Axios library to talk to Ktor server which uses a Ktor client to talk to Unsplash. The server also saves data using the Exposed library.

Project Philosophy

They want beautiful code - in this case they’re talking about DSLs. DSLs are good because they’re concise, readable and maintainable.

Kotlin is great for DSLs because it has:

Lambdas with receivers

Lambdas that are executed with the context of a receiver.

val isEven: Int.() -> Boolean = {
    this % 2 == 0
}

println(234.isEven())

This looks like an extension function, but it’s actually a lamba with receiver.

Server with Ktor

You can use DSLs to specify application behaviour in Kotlin.

A Ktor application has one or more modules. Each module has one or more features which intercept requests/responses. You can use the IntelliJ plugin to design your Ktor server and modules.

The Locations feature is a step up from manually creating router and putting gets there.

Data with Exposed

Exposed is a light-weight SQL library for Kotlin. Operations are specified with DSL and Data Access Objects.

object MyTabe : Table() {
    val id = varchar("id", 128).primaryKey()
    val userId = varchar("userId", 128) references Users.id
    val title = varchar("title", 100)
}
dbTransaction {
    Inspirations.insert {
        id[id] = UUID.randomUUID().toString()
        it[Inspirations.userId] = userId
        it[photoUrl] = photo.photoUrl
    }
}

dbTransaction here is a method that calls the lambda on the correct thread in a transaction.

Inspirations.select { Inspirations.id eq id }.mapNotNull {
    Inspiration(
        id = it[Inspirations.id],
        userId = it[Inspirations.userId]
    )
}.firstOrNull()

This looks somewhat like SQL.

Frontend with kotlinx.html

Used React as a frontend since JetBrains provides a wrapper for it.

npm install -g create-react-kotlin-app
npx create-react-kotlin-app name

To create a component:

1) Define a properties interface. 2) Define a state interface. 3) Create a component templated on those interfaces. 4) Create a render method (with kotlinx.html DSL). 5) Create a builder.

The power of types

Danny Preussler

Types

Well-typed expressions do not go wrong.

A type is a formal description of the behaviour of a program fragment. (Type systems for programming languages.)

Object oriented languages made typing so painful that so many of us gave up on them.

This caused dynamically typed languages!

Primitive Obsession

Using primitives instead of small objects for simple tasks, eg:

val user: String
val phoneNo: String

const val USER_ADMIN_ROLE = 1

Rule: Don’t put in a name what can be in a type.

suspend fun cutClip(start: Time, duration: Time): ClipId

cutClip(5.sec, 20.sec)

But out of context what does cutClip(5.sec, 20.sec) mean? Is the second parameter an end time or a duration? It should be harder to misuse.

data class PointInTime(val value: Time)
data class TimeDelta(val value: Time)

suspend fun cutClip(start: PointInTime, duration: TimeDelta): ClipId
suspend fun cutClip(start: PointInTime, duration: PointInTime): ClipId

Next example

fun priceInfo(regularPrice: Price): CharSequence
fun discountPriceInfo(discountPrice: Price): CharSequence

The domain has two different types of price - a regular and a discount one. You should create a RegularPrice and a DiscountPrice. This is Domain Driven Design.

In Soundcloud a URN is a unique identifier.

data class Urn {
    val isTrack = ...
    val isUser = ...
}

Anytime you find yourself writing code of the form “if object is of Type T1 do this, if oe type T2 do something else” slap yourself. Scott Meyers

sealed class Urn(...) {
    data class UserUrn(..): Urn(...) {
        val isValid = ...
    }

    data class CommentUrn ...
    data class TrackUrn ...
}

If you pass a boolean into a constructor that’s a code smell - it normally means you’ve got two types of that object.

val urn = UserUrn("")

Why are you able to do this?

We could throw in the constructor - but this moves the error to the runtime. (And you need to know it can throw an exception.) We could have a factory method that could return null. In domain driven design it’s normally called of and it’s on the companion of the class. In functional world these are called smart constructors.

Costs

Think of the memory costs and the GC runs.

On JVM allocations happen in Eden space, which is split into the Thread Local Allocation Buffers (no synchronisation). Therefore allocating something is just increasing a pointer. These are very small objects. The compiler might inline your class (escape analysis).

On Android, ART is optimized for allocations (Dalvik will be worse).

Allocations are OK, Types are OK, Yes even enums Jetpack, Google I/O 2018

val a: Int
val b: Int?

b will be a pointer since it’s nullable but a will just be an int.

class Age(val age: Int)
val age: Age

… I didn’t catch the point here.

Garbage Collection

The performance of the GC isn’t determined by the number of dead objects, just by the number of live objects (this is the same on Android). (Analyzing Java Memory)

Conclusion

Watch out for “hidden types”:

Look at the slides to get sources on the performance costs.