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 doconstructorFooprivate 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