https://kotlinlang.org logo
#getting-started
Title
# getting-started
j

janvladimirmostert

02/26/2023, 10:28 PM
Is there a way to use Sealed Interfaces / Classes in such a way that you can have a base sealed interface that other Sealed Interfaces / Classes can extend to get shared values?
Copy code
interface UserRepository {

    sealed interface UserRepositoryResult {
        object UserNotFound : UserRepositoryResult
        object InvalidId : UserRepositoryResult
    }

    sealed interface FindUserByIdResult : UserRepositoryResult {
        class Success(val user: User) : FindUserByIdResult
    }

    fun findUserById(id: User.Id): FindUserByIdResult

    sealed interface DeleteUserByIdResult : UserRepositoryResult {
        object Success : DeleteUserByIdResult
    }

    fun deleteUserById(id: User.Id): DeleteUserByIdResult
The above compiles, but when adding a
when
around the result of
findUserById
, it only looks for the
Success
branch
Copy code
when (this@UserRepository.findUserById(0)) {
        is UserRepository.FindUserByIdResult.Success -> TODO()
    }
it's only expecting the one branch nesting it doesn't make a difference either
Copy code
sealed interface UserRepositoryResult {
        object UserNotFound : UserRepositoryResult
        object InvalidId : UserRepositoryResult
        
        sealed interface FindUserByIdResult : UserRepositoryResult {
            class Success(val user: User) : FindUserByIdResult
        }
        sealed interface DeleteUserByIdResult : UserRepositoryResult {
            object Success : DeleteUserByIdResult
        }
    }

  
    fun findUserById(id: User.Id): UserRepositoryResult.FindUserByIdResult
    fun deleteUserById(id: User.Id): UserRepositoryResult.DeleteUserByIdResult
is there a way around this other than copying and pasting UserNotFound and InvalidId into both sealed interfaces?
1
e

Edwar D Day

02/27/2023, 7:16 AM
I would probably do it with a generic:
Copy code
sealed interface UserRepositoryResult<out T> {
    object UserNotFound : UserRepositoryResult<Nothing>
    object InvalidId : UserRepositoryResult<Nothing>
    data class Success<T>(val data: T): UserRepositoryResult<T>

    fun findUserById(id: User.Id): UserRepositoryResult<User>
    fun deleteUserById(id: User.Id): UserRepositoryResult<Unit>
}
j

janvladimirmostert

02/27/2023, 8:05 AM
that would work for this one specific case, but if if
fun dosSomethingElseById(id: User.Id): DoSomethingElseByIdResult
has more possible errors, then that error should be inside
DoSomethingElseByIdResult
including the
InvalidId
and
UserNotFound
errors Sounds like a limitation of sealed interfaces / classes
e

Edwar D Day

02/27/2023, 8:22 AM
Then maybe something like this may help you, instead of defining
UserNotFound
and
InvalidId
everywhere, you define them in a separate sealed interface, which extends the concrete result interfaces:
Copy code
sealed interface UserRepositoryResult {
    sealed interface GenericErrorResult: FindUserByIdResult, DeleteUserByIdResult{
        object UserNotFound : GenericErrorResult
        object InvalidId : GenericErrorResult        
    }
        
    sealed interface FindUserByIdResult : UserRepositoryResult {
        class Success(val user: User) : FindUserByIdResult
    }
    sealed interface DeleteUserByIdResult : UserRepositoryResult {
        object Success : DeleteUserByIdResult
    }
}

fun findUserById(id: User.Id): UserRepositoryResult.FindUserByIdResult
fun deleteUserById(id: User.Id): UserRepositoryResult.DeleteUserByIdResult
j

janvladimirmostert

02/27/2023, 9:36 AM
that's an interesting way of solving it!! instead of extending a generic interface, let the generic interface extend the specific interface. Thanks a lot!!
👍 1
3 Views