Skip to content

Kotlin Multiplatform abstraction functionally equivalent to a Kotlin value class but will compile to platform specific code

License

Notifications You must be signed in to change notification settings

05nelsonm/component-value-clazz

Repository files navigation

component-value-clazz

badge-license badge-latest-release

badge-kotlin

badge-platform-android badge-platform-jvm badge-platform-js badge-platform-js-node badge-platform-linux badge-platform-macos badge-platform-ios badge-platform-tvos badge-platform-watchos badge-platform-wasm badge-platform-windows badge-support-android-native badge-support-apple-silicon badge-support-js-ir badge-support-linux-arm badge-support-linux-mips

A small library that provides a functionally equivalent alternative to Kotlin's value class via inheritance.

Kotlin's value class is powerful and I use them pervasively, but there are some caveats to using them.

  • Kotlin value class does not compile to other languages (e.g. Java or Js). Instead, the underlying type of the value class is what consumers from those languages see. If you wish to retain the type and expose/accept it in your public API(s), this becomes problematic for non-Kotlin consumers of your code.
  • If your Kotlin value class inherits from an interface or sealed interface, it loses the boxing/unboxing properties that make them so amazing! They become equivalant to a regular class, with the downside of them not compiling to platform specific code (as mentioned above).
    • If you're using Kotlin value classes that implement interfacees, especially if they are a part of your public API(s), this library is for you!

As a library creator, I've learned that using Kotlin's value class in your public API(s) (such as kotlin.Result) comes with challenges; I've begun limiting their usage to internal only.

Ensuring that consumers of your code have a plesant experience, whether they are using Kotlin or not, was the motivation for creating this. Enjoy!

A full list of kotlin-components projects can be found HERE

Example Usages

fun main() {
    val id = "123456789"
    val userId = UserId(id)

    println(userId) // >> UserId(value=123456789)
    println(UserId.equals(id)) // >> false
    println(id.hashCode()) // >> -1867378635
    println(userId.hashCode()) // >> -1867378635
}

class UserId(val id: String): ValueClazz(id)

Usage in sealed classes

fun main() {
    println(Data.Loading) // >> Loading
    println(Data.UserId("5")) // >> UserId(value=5)
    println(Data.UserNames(listOf("matthew", "nelson"))) // >> UserNames(value=[matthew, nelson])
}

sealed class Data(value: Any): ValueClazz(value) {
    object Loading: Data(NoValue())
    class UserId(val id: String): Data(id)
    class UserNames(val names: List<String>): Data(names)
}

Objects objects objects!

fun main() {
    println(Objects.Loading.hashCode()) // >> 1859374258
    println(Objects.Success.hashCode()) // >> 807752428
    println(Objects.Loading.equals(Objects.Success)) // >> false
    println(Objects.Loading) // >> Loading
    println(Objects.Success) // >> Success
    println(Objects.Failure) // >> Failure
}

sealed class Objects: ValueClazz(NoValue()) {
    object Loading: Objects()
    object Success: Objects()
    object Failure: Objects()
}

Real world

sealed class Address(@JvmField val value: String): ValueClazz(value) {
    abstract fun canonicalHostname(): String
}

sealed class IpAddress(value: String): Address(value)

class IpV4Address
@Throws(IllegalArgumentException::class)
constructor(value: String): IpAddress(value) {

    init {
        require(value.matches(IPV4_REGEX)) {
            "$value is not a valid IPv4 address"
        }
    }

    override fun canonicalHostname(): String = value
}

class IpV6Address
@Throws(IllegalArgumentException::class)
constructor(value: String): IpAddress(value) {

    init {
        require(value.matches(IPV6_REGEX)) {
            "$value is not a valid IPv6 address"
        }
    }

    override fun canonicalHostname(): String = "[$value]"
}

Get Started

// build.gradle.kts
dependencies {
    // if ValueClazz will be a part of your public API (library devs),
    // use api instead of implementation.
    implementation("io.matthewnelson.kotlin-components:value-clazz:0.1.0")
}
// build.gradle
dependencies {
    // if ValueClazz will be a part of your public API (library devs),
    // use api instead of implementation.
    implementation "io.matthewnelson.kotlin-components:value-clazz:0.1.0"
}

Kotlin Version Compatibility

value-clazz kotlin
0.1.0 1.8.0

Git

This project utilizes git submodules. You will need to initialize them when cloning the repository via:

$ git clone --recursive https://github.com/05nelsonm/component-value-clazz.git

If you've already cloned the repository, run:

$ git checkout master
$ git pull
$ git submodule update --init

In order to keep submodules updated when pulling the latest code, run:

$ git pull --recurse-submodules