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 thevalue 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 aninterface
orsealed interface
, it loses the boxing/unboxing properties that make them so amazing! They become equivalant to a regularclass
, with the downside of them not compiling to platform specific code (as mentioned above).- If you're using Kotlin
value class
es that implementinterface
es, especially if they are a part of your public API(s), this library is for you!
- If you're using Kotlin
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
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]"
}
// 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"
}
value-clazz | kotlin |
---|---|
0.1.0 | 1.8.0 |
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