aykrieger
04/26/2025, 2:27 AMinit{ }
block and this would throw an exception if the data given in the constructor was invalid.
Example:
@JvmInline
value class Foo(
val bar: String,
) {
init {
require(bar.isNotBlank())
}
}
I was thinking I could use the Either
class and a companion object to validate the data when calling create
without throwing an exception.
@JvmInline
value class Foo(
val bar: String,
) {
companion object {
fun create(bar: String): Either<Error, Foo> =
if (bar.isNotBlank()) {
Foo(bar).right()
} else {
IsBlankString().left()
}
}
}
}
sealed interface Error {
class IsBlankString: Error
}
The issue is that I can't make the Foo
constructor Foo(val bar: String)
private for value class
or data class
. So I can't guarantee that the property bar
has been validated if someone creates Foo
with Foo("")
instead of Foo.create("")
.
I tried changing the function name from create
to invoke
to possibly override the constructor but it seems the Kotlin compiler picks the constructor Foo(val bar: String)
instead of the invoke
function when calling Foo("")
.
I understand I can achieve this with a regular class
and use a private constructor, but I haven't been able to achieve this with a value class
or data class
. I would like have the auto-generated copy()
and equals()
functions that data classes
provides.
I read this article and they have an interesting solution for this issue by using a sealed interface
to validate a data class
. When I tried this solution, I wasn't able to use the .copy()
function normally auto-generated for data class
. So there are some tradeoffs.
Link:
https://proandroiddev.com/how-to-use-arrows-either-for-exception-handling-in-your-application-a73574b39d07
Does anyone have any recommendations or solutions you have used to solve this issue?Edgar Avuzi
04/26/2025, 2:38 AMAlejandro Serrano.Mena
04/26/2025, 5:53 AMThe issue is that I can't make thewhy can't you do it? you should be able to doconstructorFoo
private forFoo(val bar: String)
orvalue class
.data class
@JvmInline
value class Foo private constructor(
val bar: String,
) {
init {
require(bar.isNotBlank())
}
}
aykrieger
04/26/2025, 8:05 PMvalue class
.
Example solution:
@JvmInline
value class Foo private constructor(
val bar: String,
) {
companion object {
operator fun invoke(bar: String): Either<Error, Foo> =
if (bar.isNotBlank()) {
Foo(bar).right()
} else {
IsBlankString().left()
}
}
}
sealed interface Error {
class IsBlankString : Error
}
For making the data class
constructor private and using the auto-generated copy()
function, I see some discussion on this and the Kotlin language recommendations.
https://youtrack.jetbrains.com/issue/KT-11914
https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-consistent-copy-visibility/
It looks like in a near future version of Kotlin, if I make the data class
constructor private, then the auto-generated copy()
function will be private too.
The Kotlin docs for data class
says
Providing explicit implementations for thehttps://kotlinlang.org/docs/data-classes.html#properties-declared-in-the-class-body I will have to manually write some function similar toandcomponentN()
functions is not allowed.copy()
copy()
and name it something else.
Thank you for the help!Artūras Šlajus
04/27/2025, 6:50 AMEdgar Avuzi
04/27/2025, 7:50 AMAlejandro Serrano.Mena
04/27/2025, 1:43 PM