How to make sealed classes generic in kotlin? - kotlin

Is it possible to use the AsyncResult class below to prevent redefining InFlight, Error and InFlight in UserDataAppResult and CreateUserResult?
//TODO: use this to make the below classes generic?
sealed class AsyncResult{
object InFlight : AsyncResult()
data class Error(val errorMessage: String) : AsyncResult()
data class Loaded<out T>(val users: T) : AsyncResult()
}
sealed class UserDataAppResult : AppResult() {
object InFlight : UserDataAppResult()
data class Error(val errorMessage: String) : UserDataAppResult()
data class Loaded(val users: List<User>) : UserDataAppResult()
}
sealed class CreateUserResult : AppResult() {
object InFlight : CreateUserResult()
data class Error(val errorMessage: String) : CreateUserResult()
data class Loaded(val users: User) : CreateUserResult()
}
Is it possible for the above code to look like this?
sealed class AsyncResult{
class InFlight : AsyncResult()
data class Error(val errorMessage: String) : AsyncResult()
data class Loaded<out T>(val users: T) : AsyncResult()
}
sealed class UserDataAppResult : AsyncResult()
sealed class CreateUserResult : AppResult()
val activeUsers: Flowable<UserDataAppResult> = appDatabase.userDao().getActiveUsers(appSettings.currentLanguage.ordinal)
.map<UserDataAppResult> { UserDataAppResult.Loaded(it) }
.onErrorReturn { UserDataAppResult.Error(it.localizedMessage) }
.startWith(UserDataAppResult.InFlight)
.observeOn(AndroidSchedulers.mainThread())
.share()
fun createUser(): Flowable<CreateUserResult> {
val userId = UUID.randomUUID().toString()
val user = User()
user.id = userId
return appDatabase.userDao().insertAll(user)
.map <CreateUserResult> { CreateUserResult.Loaded(user) }
.onErrorReturn { CreateUserResult.Error(it.localizedMessage) }
.startWith(CreateUserResult.InFlight)
}
Currently UserDataAppResult.Error is not found which makes sense.
But is it possible to reuse the AppResult sealed class hierarchy and introduce new types.

your Object can't have a generic type in Kotlin but this could be solved simply by following the example below:
sealed class ResponseState<out T> {
object Loading : ResponseState<Nothing>()
data class Error(val throwable: Throwable) : ResponseState<Nothing>()
data class Success<T>(val item: T) : ResponseState<T>()
}
writing:
val _state = MutableLiveData<ResponseState<MessageModle>>()
_state.postValue(ResponseState.Loading)
myNetworkCall { response, e
if (e != null) _state.postValue(ResponseState.Error(e))
else _state.postValue(ResponseState.Success(response))
}
reading:
state.observe(..., {state ->
when(state) {
Loading -> showLoading()
is Error -> showError(state.throwable)
is Success -> onSuccess(state.item)
}
}

It's not possible in Kotlin. Every type you use must have an explicitly declared class somewhere. Classes are not created implicitly by the compiler even in the case when nested classes are declared in the superclass.
For your problem, I recommend you rewrite the code from combining two inheritance-based hierarchies to one of the two combining inheritance and composition, or just restructure the hierarchy in some way, for example (I suppose the exact instance of a result would be irrelevant to you in case when it's not Loaded):
sealed class AsyncResult {
object InFlight : AsyncResult()
data class Error(val errorMessage: String) : AsyncResult()
sealed class Loaded<out T>(val result: T) : AsyncResult() {
sealed class UserDataAppResult(users: List<User>) : Loaded<List<User>>(users)
sealed class CreateUserResult(user: User) : Loaded<User>(user)
}
}

Via the Google Guidelines: https://developer.android.com/jetpack/docs/guide
sealed class Resource<T>(
val data: T? = null,
val message: String? = null
) {
class Success<T>(data: T) : Resource<T>(data)
class Loading<T>(data: T? = null, var refreshing: Boolean = false) : Resource<T>(data)
class Error<T>(data: T? = null, message: String) : Resource<T>(data, message)
}

Inspired by solution from #kosh
-> ViewState:
sealed class ViewState<out T> {
object Loading : ViewState<Nothing>()
data class Error(val throwable: Throwable) : ViewState<Nothing>()
data class Success<T>(val item: T) : ViewState<T>()
}
-> inside ViewModel:
private val _homeVS = MutableLiveData<ViewState<HomeMode>>()
val homeVS: LiveData<ViewState<HomeMode>> get() = _homeVS
// start requesting API
_homeVS.value = ViewState.Loading
try {
val result = loadData()
_homeVS.value = ViewState.Success(result)
} catch (e: Exception) {
_homeVS.value = ViewState.Error(e)
}
Then you can use this generic in layout/View
-> View:
viewModel.homeVS.observe(viewLifecycleOwner, {state ->
when(state) {
is ViewState.Error -> showError(state.throwable)
is ViewState.Success -> onSuccess(state.item)
ViewState.Loading -> showLoading()
}
})
-> On layout we may need little mor tuning
sealed class ViewState<out T> {
object Loading : ViewState<Nothing>()
data class Error(val throwable: Throwable) : ViewState<Nothing>()
data class Success<T>(val item: T) : ViewState<T>()
fun toData(): T? = (this as? Success)?.item
}
toData provides data only onSuccess
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='#{vm.homeVS.toData() != null ? vm.homeVS.toData().param1 : ""}' />
<!--onLoading-->
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility='#{vm.homeVS instanceof ViewState.Loading ? View.VISIBLE : View.GONE}' />
<!--onError-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility='#{vm.homeVS instanceof ViewState.Error ? View.VISIBLE : View.GONE}' />
Surely with BindingAdapter, you can make it even better. Here just for illustrating solution.
Good luck,'.

Related

Kotlin - make multiple sealed classes have common set of "base" subclasses, but each could still add it's specific ones

This question has a wider scope than Extract common objects from sealed class in kotlin and Android - How to make sealed class extend other sealed class? so it's not a duplicate of these
I have multiple sealed classes that represent results of various API calls. Each of these calls has a common set of expected results (success, network error, unexpected error), but each could introduce it's own result types (like 'user not found' or 'wrong ID').
To avoid copying same subclasses to each of sealed class, I want to create a "base" type that includes all common result types, while each sealed class could add it's specific subclasses:
interface BaseApiCallResult {
data class Success(val data: String) : BaseApiCallResult
data class UnexpectedError(val error: Throwable) : BaseApiCallResult
data class NetworkError(val error: ApolloException) : BaseApiCallResult
}
sealed class ApiCallResult1 : BaseApiCallResult {
data class WrongID(val id: Int) : ApiCallResult1()
}
sealed class ApiCallResult2 : BaseApiCallResult {
data class UserDoesNotExist(val userid: Long) : ApiCallResult2()
}
sealed class ApiCallResult3 : BaseApiCallResult {
data class NameAlreadyTaken(val name: String) : ApiCallResult3()
}
the problem is that subclasses in "base" cannot be treated as "child" classes:
fun apiCall1(): ApiCallResult1 {
// won't compile, since BaseApiCallResult.UnexpectedError is not ApiCallResult1
return BaseApiCallResult.UnexpectedError(Exception(""))
}
fun useApi() {
when(val result = apiCall1()) {
is ApiCallResult1.WrongID -> { }
// compile error: Incompatible types
is BaseApiCallResult.Success -> { }
is BaseApiCallResult.UnexpectedError -> { }
is BaseApiCallResult.NetworkError -> { }
}
}
solution from Android - How to make sealed class extend other sealed class? might be applied here, but for big number of sealed classes (I expect I might need several dozen of such classes) it becomes rather hacky
interface BaseApiCallResult {
data class Success(val data: String) : Everything
data class UnexpectedError(val error: Throwable) : Everything
data class NetworkError(val error: ApolloException) : Everything
}
sealed interface ApiCallResult1 : BaseApiCallResult {
data class WrongID(val id: Int) : ApiCallResult1()
}
sealed interface ApiCallResult2 : BaseApiCallResult {
data class UserDoesNotExist(val userid: Long) : ApiCallResult2
}
sealed interface ApiCallResult3 : BaseApiCallResult {
data class NameAlreadyTaken(val name: String) : ApiCallResult3
}
// adding each new sealed interface here seems like a hack
interface Everything : BaseApiCallResult, ApiCallResult1, ApiCallResult2, ApiCallResult3
Additionally, with above solution, every when {...} complains about Everything case not being handled. I could resign from using Everything, but then I have to list all interfaces in each "base" subclass, which is simply terrible:
// just imagine how would it look if there were 30 ApiCallResult classes
interface BaseApiCallResult {
data class Success(val data: String) : BaseApiCallResult, ApiCallResult1, ApiCallResult2, ApiCallResult3
data class UnexpectedError(val error: Throwable) : BaseApiCallResult, ApiCallResult1, ApiCallResult2, ApiCallResult3
data class NetworkError(val error: ApolloException) : BaseApiCallResult, ApiCallResult1, ApiCallResult2, ApiCallResult3
}
Is there a better way to handle this kind of situation ?
You have to separate ApiResult from ApiMethodResult they should not to be relatives.
Kotlin already has type Result and you can use it:
sealed interface ApiCall1Result {
class WrongID : ApiCall1Result
class UserInfo(val userId: Int) : ApiCall1Result
}
fun api1() : Result<ApiCallResult>
fun useApi1() {
val result = api1()
if(result.isFailure) {
handle failure
} else {
val apiResult = result.getOrThrow()
when(apiResult) {
is WrongID -> {}
is UserInfo -> {}
}
}
}
Or you can implement it by your self:
interface ApiResult<in T> {
class Success<T : Any>(val data: T) : ApiResult<T>
class Fail(val error: Throwable) : ApiResult<Any>
}
sealed class ApiCallResult1 {
class WrongID(val id: Int) : ApiCallResult1()
class UserInfo(val id: Int, val name: String) : ApiCallResult1()
}
fun apiCall1(): ApiResult<ApiCallResult1> {
return ApiResult.Fail(Throwable())
}
fun useApi() {
when (val result = apiCall1()) {
is ApiResult.Fail -> {}
is ApiResult.Success -> when (result.data) {
is ApiCallResult1.WrongID -> {}
is ApiCallResult1.UserInfo -> {}
}
}
}
You could create a generic type for the sealed interface, and this type gets wrapped by one additional child class:
interface ApiCallResult<out O> {
data class Success(val data: String) : ApiCallResult<Nothing>
data class UnexpectedError(val error: Throwable) : ApiCallResult<Nothing>
data class NetworkError(val error: ApolloException) : ApiCallResult<Nothing>
data class Other<out O>(val value: O): ApiCallResult<O>
}
Then you can define your other callback types using a specific class as the O type:
data class UserDoesNotExist(val userid: Long)
fun handleApiCallResult2(result: ApiCallResult<UserDoesNotExist>) {
when (result) {
is ApiCallResult.Success -> {}
is ApiCallResult.UnexpectedError -> {}
is ApiCallResult.NetworkError -> {}
is ApiCallResult.Other -> {
// do something with result.value
}
}
}
When you have more than one other case, you can create a sealed interface to be the parent of those other cases, but you'll unfortunately need a nested when to handle them.
When you have no other cases, you can use ApiCallResult<Nothing> as your response type, but you'll unfortunately need to leave a do-nothing {} branch for the Other case. Or you could set up a separate sealed interface like in your long-winded final solution in your question, which is manageable because it would never grow to more than two sealed types.

How to add generic Throwable on a method signature

How can I create a Map (it that's the best way) of having something for instance :
400 to CustomExceptionFor400
500 to CustomExceptionFor500
The first param is an Int and it's a HttpStatusCode, and the value is something like this
sealed class OrganizationExceptions : Exception() {
object OrganizationNotFound : OrganizationExceptions()
object ListNotAvailable : OrganizationExceptions()
}
This is an example, but it won't be always OrganizationExceptions I'm creating a method generic, also I don't know if it's better to create a sealed class or create
class OrganizationNotFoundException : Throwable() //or Exception()
class ListNotAvailable : Throwable() //or Exception()
Any recomendations?
the method signature is :
fun apiCallWithStatusCode(codes : HashMap<Int, Throwable>, apiCall : suspend () -> Response<T>,):Result<Unit>{...}
So my method should return a Result so it means that if the HttpStatusCode is 400 I should return return Result.failure(CustomExceptionFor400) that is the one that should come in the codes from the method.
PSEUDO EXAMPLE OF WHAT I WOULD LIKE TO HAVE
suspend fun <T : Throwable> apiCallWithStatusCode(
codes: Map<Int, T>,
apiCall: suspend () -> Response<T>,
): Result<Unit> {
runCatching { apiCall() }
.fold(
onSuccess = { response ->
if (response.isSuccessful) Result.success(Unit)
return codes[response.code()]?.let {
Result.failure(it)
} ?: Result.failure(GeneralError)
},
onFailure = {
return when (it) {
//Return Result.failure(WhateverError) this is done already
}
}
)
}
But I don't know if that's the way to do it.
My questions are :
What's better
sealed class OrganizationExceptions : Exception() {
object OrganizationNotFound : OrganizationExceptions()
object ListNotAvailable : OrganizationExceptions()
}
Or
class OrganizationNotFoundException : Throwable() //or Exception()
class ListNotAvailable : Throwable() //or Exception()
Then after knowing this, is to create this generic function because now there are two "OrganizationNotFoundException" and "ListNotAvailable" but perhaps other feature of my app have different Exceptions so that's why I want to have a generic one.

sealed class extends other sealed class

I am making various state models and side effect classes while using the MVI pattern. The state model can reduce the boilerplate by extending the interface that collects common parts, but the side effect class does not support extends as it is a sealed class. So I see the Toast side effect as boilerplate code. How can I get rid of this Toast side effect boilerplate code? Is there any way for the sealed class to extend to other sealed classes?
My state classes:
interface BaseMviState {
val loaded: Boolean
val exception: Exception?
fun isException() = exception != null
}
data class MviJoinState(
override val loaded: Boolean = false,
override val exception: Exception? = null,
val loginResult: Boolean = false,
val registerResult: Boolean = false,
) : BaseMviState
data class MviRoomCreateState(
override val loaded: Boolean = false,
override val exception: Exception? = null,
) : BaseMviState
My side effect classes:
sealed class MviJoinSideEffect {
data class SetupAutoLogin(val user: User) : MviJoinSideEffect()
data class Toast(val message: String) : MviJoinSideEffect() // boilerplate
}
sealed class MviRoomCreateSideEffect {
data class Toast(val message: String) : MviRoomCreateSideEffect() // boilerplate
}
I solved this problem by extending the interface in a sealed class like this:
sealed interface BaseEvent {
data class Toast(val toastMessage: String) : BaseEvent
}
sealed class Event : BaseEvent {
data class Snackbar(val snackbarMessage: String) : Event()
}
fun main() {
val event: BaseEvent = Event.Snackbar("Hi")
when (event) {
is BaseEvent.Toast -> todo(event.toastMessage)
is Event.Snackbar -> todo(event.snackbarMessage)
}
}
fun todo(message: String) {}

How do i serialize a generic sealed class with kotlinx.serialization

Not sure if it is possible yet but for the life of me I cannot figure out how to serialize this.
sealed class ServiceResult<out T : Any> {
data class Success<out T : Any>(val data: T) : ServiceResult<T>()
data class Error(val exception: Exception) : ServiceResult<Nothing>()
}
Everything that is stuff into T is using #Serializable ex:
#Serializable
data class GalleryDTO(
override val id: Int,
override val dateCreated: Long,
override val dateUpdated: Long,
val name:String,
val description:String,
val photos:List<DTOMin>
) : DTO
As Animesh Sahu already mentioned there is an issue for this topic that is still open, but the solution using a surrogate suggested by Михаил Нафталь for serialization of Error can actually be used also to serialize the polymorphic ServiceResult, by creating a surrogate that mixes the fields of Success and Error. For the sake of simplicity in the example I only represent the exception message.
#Serializable(with = ServiceResultSerializer::class)
sealed class ServiceResult<out T : Any> {
data class Success<out T : Any>(val data: T) : ServiceResult<T>()
data class Error(val exceptionMessage: String?) : ServiceResult<Nothing>()
}
class ServiceResultSerializer<T : Any>(
tSerializer: KSerializer<T>
) : KSerializer<ServiceResult<T>> {
#Serializable
#SerialName("ServiceResult")
data class ServiceResultSurrogate<T : Any>(
val type: Type,
// The annotation is not necessary, but it avoids serializing "data = null"
// for "Error" results.
#EncodeDefault(EncodeDefault.Mode.NEVER)
val data: T? = null,
#EncodeDefault(EncodeDefault.Mode.NEVER)
val exceptionMessage: String? = null
) {
enum class Type { SUCCESS, ERROR }
}
private val surrogateSerializer = ServiceResultSurrogate.serializer(tSerializer)
override val descriptor: SerialDescriptor = surrogateSerializer.descriptor
override fun deserialize(decoder: Decoder): ServiceResult<T> {
val surrogate = surrogateSerializer.deserialize(decoder)
return when (surrogate.type) {
ServiceResultSurrogate.Type.SUCCESS ->
if (surrogate.data != null)
ServiceResult.Success(surrogate.data)
else
throw SerializationException("Missing data for successful result")
ServiceResultSurrogate.Type.ERROR ->
ServiceResult.Error(surrogate.exceptionMessage)
}
}
override fun serialize(encoder: Encoder, value: ServiceResult<T>) {
val surrogate = when (value) {
is ServiceResult.Error -> ServiceResultSurrogate(
ServiceResultSurrogate.Type.ERROR,
exceptionMessage = value.exceptionMessage
)
is ServiceResult.Success -> ServiceResultSurrogate(
ServiceResultSurrogate.Type.SUCCESS,
data = value.data
)
}
surrogateSerializer.serialize(encoder, surrogate)
}
}
This solution can also be easily extended to support nullable Ts. In this case when deserializing you will also have to check if null is a valid value for T (it can be done by checking descriptor.isNullable on tSerializer) and you will also have to cast data as T.
Polymorphic serialization will be a mess in this case (you will have to manually register all possible types passed as a generic parameter to ServiceResult<T>), and will have several limitations (it would be impossible to register primitive types (including Nothing and String) as generic parameters, for instance).
If you only need serialization (aka encoding), I'd recommend to serialize both subtypes independently (for convenience, wrap subtype determination into auxilary function):
inline fun <reified T : Any> serializeServiceResult(x: ServiceResult<T>) = when (x) {
is ServiceResult.Success -> Json.encodeToString(x)
is ServiceResult.Error -> Json.encodeToString(x)
}
To serialize ServiceResult.Success you need just to mark it with #Serializable annotation. The tricky part here is serialization of ServiceResult.Error, or more precisely, serialization of its exception: Exception field. I'd suggest to serialize only its message (via surrogate):
sealed class ServiceResult<out T : Any> {
#Serializable
data class Success<out T : Any>(val data: T) : ServiceResult<T>()
#Serializable(with = ErrorSerializer::class)
data class Error(val exception: Exception) : ServiceResult<Nothing>()
}
#Serializable
private data class ErrorSurrogate(val error: String)
class ErrorSerializer : KSerializer<ServiceResult.Error> {
override val descriptor: SerialDescriptor = ErrorSurrogate.serializer().descriptor
override fun deserialize(decoder: Decoder): ServiceResult.Error {
val surrogate = decoder.decodeSerializableValue(ErrorSurrogate.serializer())
return ServiceResult.Error(Exception(surrogate.error))
}
override fun serialize(encoder: Encoder, value: ServiceResult.Error) {
val surrogate = ErrorSurrogate(value.exception.toString())
encoder.encodeSerializableValue(ErrorSurrogate.serializer(), surrogate)
}
}

Kotlin secondary constructor with generic type

In java
I can achieve two constructors like
public TargetTitleEntryController() { }
public <T extends Controller & TargetTitleEntryControllerListener> TargetTitleEntryController(T targetController) {
setTargetController(targetController);
}
I want to convert it to Kotlin
class TargetTitleEntryController ()
with the secondary constructor. I don't know how to declare with generic type like Java counterpart.
There is no intersection types in Kotlin (sad)
But there is Generic constraints (hope)
But Generic constraints not applicable in the secondary constructor (sad)
But you can simulate secondary constructor in a companion object using Invoke operator overloading (workaround):
class TargetTitleEntryController {
// ...
companion object {
operator fun <T> invoke(targetController: T): TargetTitleEntryController
where T : Controller,
T : TargetTitleEntryControllerListener {
return TargetTitleEntryController().apply {
setTargetController(targetController)
}
}
}
}
Here is an example where you specify a Type T which implements two interfaces (CharSequence, Runnable):
class Person<T>(val name: String) where T : CharSequence, T : Runnable {
constructor(name: String, parent: T) : this(name) {
}
}
So actually something like this should work:
class TargetTitleEntryController<T> () where T : Controller, T : TargetTitleEntryControllerListener {
constructor(targetController: T) : this() {
}
}
You can do it like this :)
class TargetTitleEntryController <T>() : Controller() where T: Controller, T: TargetTitleEntryControllerListener<T> {
constructor(target: T) : this() {
targetController = target
}
}
you can implement it in your parent controller like this:
class TargetDisplayController : Controller(), TargetTitleEntryControllerListener<TargetDisplayController> {
var targetTitleEntryController = TargetTitleEntryController(this)
override fun onTitlePicked(String option) {
}
override fun onAttach(view: View) {
// push controller here
}
}