How do I override sealed class fields? - kotlin

I created a custom Result field (And not Kotlin's Result) in my service so I can return a message field in Both Success & Failure Cases:
sealed class Result<T> {
data class Success<T>(val value: T, val message: String) : Result<T>()
data class Failure<T>(val throwable: Throwable? = null, val message: String) : Result<T>() {
val isExceptional = throwable != null
val error: Throwable
get() = throwable ?: error("Error is undefined in [$this]")
}
}
Then in another class I called a method that produces this result and wanted to log Result.message
logger.info { "Finished with message [${result.message}]." }
Only, kotlin compiler doesn't recognize "message" since it's not a property of Result directly, but a property of Success and Failure.
I tried to override the message field and define it in Result class. But I get an Error.
Error:(10, 38) Kotlin: 'message' in 'Result' is final and cannot be overridden
So, how can one accesss Result.message without casting the result instance to it's derived implementing class (Success or Failure)?

A clean solution that I found is the following.
While Kotlin does not allow us to override sealed class members. It does allow us to override interface members.
So, I Created a simple interface for the message field, and implemented it from Result class:
interface ResultMessage {
val message: String
}
sealed class Result<T> : ResultMessage
// Now I'm able to override the message field simply, with no error
data class Success<T>(val value: T, override val message: String) : Result<T>()
data class Failure<T>(val throwable: Throwable? = null, override val message: String) : Result<T>() {
val isExceptional = throwable != null
val error: Throwable
get() = throwable ?: error("Error is undefined in [$this]")
}

You can declare an abstract property in the Result class:
sealed class Result<T> {
abstract val message: String
data class Success<T>(val value: T, override val message: String) : Result<T>()
data class Failure<T>(val throwable: Throwable? = null, override val message: String) : Result<T>() {
val isExceptional = throwable != null
val error: Throwable
get() = throwable ?: error("Error is undefined in [$this]")
}
}
Though, your solution is also an option

1- you can mark them open
open val message: String = ""
2- you can define them abstract
abstract val message: String

Related

Access Data class property while using Generic Type parameter <T> in runtime [Kotlin]

I'm trying to return a property/value from Data class by checking type parameter
Data Class :
data class SystemConfiguration(
val systemName: String,
val fields: List<String>
)
Abstract class :
abstract class ConfigurationLoader<out T> {
abstract val clazz: Class<out T>
private fun getAssociateAttribute(file: T): String{
return when(clazz){
SystemConfiguration::class.java -> file.systemName // Line causing error
// If its another Data class, I should return value from that data class
else -> ""
}
}
open fun loadConfigurations(): Map<String, T>{
val map = Paths.get(configurationFolderPath, filePath).toFile().walkTopDown().filter { it.isFile }.map {
val x = ionSystem.loader.load(it)[0]
ionValueMapper.readValue<List<T>>(x.toString())
}.flatten().associateBy { getAssociateAttribute(it) }
}
}
inline fun <reified T: Any> javaClasstype(): Class<T> {
return T::class.java
}
Sub class
class ServiceConfigurationLoader (
override val clazz: Class<SystemConfiguration> = javaClasstype()
): ConfigurationLoader<SystemConfiguration>()
I'm getting an exception "e: Unresolved reference: systemName". Not able to access values inside data class while we use type parameter
If i use like this(directly mentioning data class name), I'm able to access the values
private fun getAssociateAttribute(file: SystemConfiguration): String{
return when(clazz){
SystemConfiguration::class.java -> file.systemName
else -> ""
}
}
Could someone help me out here to access the value using Type paramater in Kotlin?
Thanks in Advance !!
I have tried using reified keyword as well. Still getting the same issue. I'm expecting to access the value using Type paramater

Kotlin class generic type infered as nullable

A generic class for holding network request result
sealed class Result<out T : Any?> {
data class Success<out T : Any?>(val data: T) : Result<T>()
data class Error(val message: String, val exception: Exception? = null) : Result<Nothing>()
}
A generic function for encapsulating network result into Result.
It is called from a repository and passes a retrofit2 api call as an input parameter
suspend fun <T: Any?> request(method: Call<T>): Result<T> {
return withContext(Dispatchers.IO) {
try {
val response = method.awaitResponse() // Retrofit2 Call
if (response.isSuccessful)
Result.Success(response.body())
else
response.getErrorResult()
} catch (e: Exception) {
Result.Error(e.message.orEmpty(), e)
}
}
// Type mismatch.
// Required: Result<T>
// Found: Result<T?>
}
It is called like this
interface Webservice {
#GET("data")
fun getData(): Call<Data> // Retrofit2
}
suspend fun getData(): Result<Data> {
return request(webservice.getData())
}
Why does it infer the result as type T? but not T?
The problem is this line:
Result.Success(response.body())
body is marked with Nullable, so when ported to Kotlin, response.body() returns a T?, rather than T. See here for how this works.
Therefore, the type parameter for Result.Success is inferred to be T?, and so the expression above creates a Result<T?>. Note that the T in Result<T?> refers to the type parameter of request.

How to return Generic List in FallbackHandler using Kotlin

I am trying to return List with Generic type from handle(context: ExecutionContext?) method of MicroProfile FallbackHandler using Kotlin. But it's throwing exception like " org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException: Invalid #Fallback on getDistanceData(java.lang.String): fallback handler's type java.util.List<? extends java.lang.String> is not the same as method's return type
"
RestClient :
#GET
#Path("/testFallbackUrl")
#Fallback(DistanceServiceFallback::class)
#CircuitBreaker(
requestVolumeThreshold = 4, failureRatio = 0.75, delay = 5000, successThreshold = 3
)
fun getDistanceData(#QueryParam("date") date: String) : List<String>
Handler:
#RegisterForReflection
class DistanceServiceFallback : FallbackHandler<List<String>> {
#field:Default
#field:Inject
lateinit var logger: Logger
override fun handle(context: ExecutionContext?): List<String> {
logger.error("Inside DistanceServiceFallback handler. ")
return listOf("Hello")
}
}
This is because of a difference in type inference when your kotlin code is processed in Java. The return type of getDistanceData is java.util.List<String> and the handler's return type is as mentioned in the exception java.util.List<? extends java.lang.String>.
The return type java.util.List<String> is observed from the CDI interceptor so I am not sure how exactly it is extracted but obviously it is not carrying <? extends E> information.
The handler's type java.util.List<? extends java.lang.String> is on the other hand extracted from kotlin.collections.List which is defined as public interface List<out E> : Collection<E> which I think is correct as <out E> should translate to <? extends E>.
However, there is an easy workaround:
#GET
#Path("/testFallbackUrl")
#Fallback(DistanceServiceFallback::class)
#CircuitBreaker(
requestVolumeThreshold = 4, failureRatio = 0.75, delay = 5000, successThreshold = 3
)
fun getDistanceData(#QueryParam("date") date: String) : MutableList<String> {
return mutableListOf("Default")
}
#RegisterForReflection
class DistanceServiceFallback : FallbackHandler<MutableList<String>> {
#field:Default
#field:Inject
lateinit var logger: Logger
override fun handle(context: ExecutionContext?): MutableList<String> {
logger.error("Inside DistanceServiceFallback handler. ")
return mutableListOf("Hello")
}
}
which works because MutableList is defined as public interface MutableList<E> : List<E>, MutableCollection<E> and thus it has now an exact generic type.

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)
}
}

How to make sealed classes generic in 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,'.