How can I know which the subclass of sealed class will return when I use Compose in Android Studio? - kotlin

The Result<out R> is a sealed class which hold three subclass Success, Error and Loading.
The fun Greeting is #Composable.
By my design, I define queryList as Result class, and it is assigned as Loading first, then it will be Success or Error.
1: But the following code can't be compiled as the following error information, what's wrong with my Code?
2: Is there a better solution for my design?
Compile error
Property delegate must have a 'getValue(Nothing?, KProperty>)' method. None of the following functions are suitable.*
#Composable
fun Greeting(
name: String,
mViewMode:SoundViewModel= viewModel()
) {
Column() {
//The following code cause error.
val queryList by produceState(initialValue = Result<Flow<List<MRecord>>>.Loading ) {
value = mViewMode.listRecord()
}
when (queryList){
is Loading -> { ...}
is Error -> { ...}
is Success -> {...}
}
}
}
class SoundViewModel #Inject constructor(): ViewModel()
{
fun listRecord(): Result<Flow<List<MRecord>>>{
return aSoundMeter.listRecord()
}
}
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}

Since queryList is backed by a delegate, it can not be final.
This means in theory, each time you access it, it might hold a different value. The kotlin compiler is very pessimistic about this and assumes that between the time the is Result.Success branch of your when statement is selected and val mydata = queryList.data is executed, the value of queryList might have changed.
To solve this, you can assign the current value of queryList to a final variable and work with that one instead:
when (val currentList = queryList) {
is Result.Error -> {}
is Result.Loading -> {}
is Result.Success -> {
SomeComposable(currentList.data) //currentList is properly smart-cast to Result.Success
}
}

Related

Type mismatch using generics

I have issues understanding generics and i failed to find the answer here.
Here is the issue:
I have an abstract class that is suppose to be the parent class to few viewmodels. Idea is, one view is going to be created based on data coming from different viewmodels.
And i want to draw it based on the same method, just using different Types.
Also, I dont want a return type, i want to trigger some callbacks.
Here is the abstract:
package foo.bar.ui.app.user_profile.view_model
abstract class UserDefaultsGenericViewModel : BaseViewModel() {
abstract fun <P> getData(
data: (P) -> Unit,
error: (Result.Error) -> Unit
)
}
And then example of one ViewModel is like this:
package foo.bar.ui.app.user_profile.view_model
#HiltViewModel
class StopViewModel #Inject constructor(
private val getStopsByRouteUseCase: ParamsUseCase<RouteParams, Stops>
) : UserDefaultsGenericViewModel() {
var stopId = ""
override fun <Stops> getData(data: (Stops) -> Unit, error: (Result.Error) -> Unit) {
viewModelScope.launch {
when (val resultStops = getStopsByRouteUseCase.invoke(RouteParams(stopId, Direction.ToWork))) {
is Result.Success -> {
data.invoke(resultStops.value)
}
is Result.Error -> Log.e("TAG", "bar")
}
}
}
}
The problem is in this line:
data.invoke(resultStops.value)
Im getting:
Type mismatch: inferred type is foo.bar.onboarding.Stops but Stops#1 (type parameter of foo.bar.ui.app.user_profile.view_model.StopViewModel.getData) was expected
What am i doing wrong?
You're using a generic method, but it looks like you want a generic class/interface.
In your override fun <Stops> getData, Stops is an arbitrary name of a type parameter, not the actual Stops type that you seem to want. What you probably want instead is the following:
// note the <P> at the class level
abstract class UserDefaultsGenericViewModel<P> : BaseViewModel() {
// no <P> here after the fun keyword
abstract fun getData(
data: (P) -> Unit,
error: (Result.Error) -> Unit
)
}
#HiltViewModel
class StopViewModel #Inject constructor(
private val getStopsByRouteUseCase: ParamsUseCase<RouteParams, Stops>
) : UserDefaultsGenericViewModel<Stops>() { // note the <Stops> type argument here
...
}
Here the <P> is on the class declaration, so that <P> is determined once for each instance of the class. If you declare the generic on the method, the actual type can be different for each method invocation.

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.

Generics in Objects

I have a question about sealed class, generics and object.
Let's say I would like to model something like 3 finite cases with a sealed class something like this:
sealed class ChangeState<S> {
fun reduceState(state: S): S
}
data class SetState<S>(val newState: S) : ChangeState<S>() {
override fun reduce(state: S): S = newState
}
object NoStateChange : ChangeState<Nothing>() { // What do I specify here for ChangeState? Nothing?
override fun reduce(state: Nothing): Nothing {
throw Exception("This should never be called")
}
}
The goal is to provide a convenient way to define NoStateChange in a generic way that it can be used as following:
fun foo(i : Int) : ChangeState<Int> {
return if (i==0)
NoStateChange // Won't compile because return type is ChangeState<Nothing> but expected ChangeState<Int>
else
SetState(i)
}
Is there a way to do that with object and Generics somehow?
As pointed out by #Tenfour04 the issue is that out is needed but reduceState() would require in as well. However, reduceState() can be refactored out of the class hierarchy and moved to an extension function like that:
sealed class ChangeState<out S>
data class SetState<S>(val newState: S) : ChangeState<S>()
object NoStateChange : ChangeState<Nothing>()
fun <S> ChangeState<S>.reduce(state: S): S {
return when (val change = this) {
is SetState -> change.newState
is NoStateChange -> state
}
}

Kotlin type inference on "supposedly" right types

I am new to Kotlin and I was playing with it. I pretty much wanted to create a pretty basic event bus. So I came up with this
interface Event
interface EventListener<E : Event> {
fun handle(event: E)
}
interface EventBus {
fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>)
}
class MyBus() : EventBus {
private val eventListeners: MutableMap<String, MutableList<EventListener<out Event>>> = mutableMapOf()
constructor(listeners: List<Pair<Class<Event>, EventListener<Event>>>) : this() {
listeners.forEach {
registerListener(it.first, it.second)
}
}
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val key = aClass.name
val listeners: MutableList<EventListener<out Event>> = eventListeners.getOrPut(key) { mutableListOf() }
listeners.add(eventListener)
}
}
val bus = MyBus(
listOf(
MyEvent::class.java to MyEventListener()
)
)
class MyEvent : Event
class AnotherEvent : Event
class MyEventListener : EventListener<MyEvent> {
override fun handle(event: MyEvent) {
}
}
what happens is that when I try to create MyBus using the constructor accepting the list of pairs, I get
Type inference failed. Expected type mismatch: inferred type is List<Pair<Class<MyEvent>,MyEventListener>> but List<Pair<Class<Event>,EventListener<Event>>> was expected
But if I change the constructor to be something like
constructor(listeners: List<Pair<Class<out Event>, EventListener<out Event>>>) : this() {
listeners.forEach {
registerListener(it.first, it.second)
}
}
adding out pretty much everywhere, then the MyBus constructor works, but the invocation to registerListener(..) breaks for the same exact reason as before. So the only way to solve this is to add "out"s also on registerListener function.
I suspect I'm doing something wrong here, but I don't know what precisely. Any help?
If you want your EventListener to be able to consume Events, then its type has to be invariant or covariant (not declared out). If it let you pass your EventListener<MyEvent> as if it were an EventListener<Event>, then your MyBus class might call listener.handle(event) on it with some Event that is not a MyEvent, such as AnotherEvent. Then you will get a ClassCastException when it tries to cast this AnotherEvent to MyEvent.
To be able to store different types of invariant EventHandlers, you will have to remove the variance restrictions by using star projection, and cast them when you retrieve them from the map. So make the map keys into class objects instead of just Strings. Since you will not have the help of the compiler when working with the star-projected types, you need to be careful that you are only adding an item to your MutableMap that is of the same type as the Class key that's associated with it. Then when you retrieve items, only cast to an invariant type.
The other part of your issue is that your constructor needs a generic type. Right now it works exclusively with Event so it can't handle subtypes of Event. Kotlin doesn't (yet?) support generic types for constructors so you have to do this with a factory function.
Here's an example of all the above.
class MyBus() : EventBus {
private val eventListeners: MutableMap<Class<*>, MutableList<EventListener<*>>> = mutableMapOf()
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val listeners = retrieveListeners(aClass)
listeners.add(eventListener)
}
private fun <E: Event> retrieveListeners(aClass: Class<E>): MutableList<EventListener<E>> {
#Suppress("UNCHECKED_CAST")
return eventListeners.getOrPut(aClass) { mutableListOf() } as MutableList<EventListener<E>>
}
}
// Factory function
fun <E : Event> myBusOf(listeners: List<Pair<Class<E>, EventListener<E>>>): MyBus {
return MyBus().apply {
listeners.forEach {
registerListener(it.first, it.second)
}
}
}
And you might want to change the type of the factory parameter from a <List>Pair to a vararg Pair so it's easier to use.
Here's a stripped down example to explain the variance limitation.
Your interface for an Event consumer:
interface EventListener<E : Event> {
fun handle(event: E)
}
Two implementations of Event:
class HelloEvent: Event {
fun sayHello() = println("Hello world")
}
class BoringEvent: Event {}
A class implementing the interface:
class HelloEventListener: EventListener<HelloEvent> {
override fun handle(event: HelloEvent) {
event.sayHello()
}
}
Now you have an EventListener that can handle only HelloEvents. Try to treat it like an EventListener<Event>:
val eventListener: EventListener<Event> = HelloEventListener() // COMPILE ERROR!
Imagine the compiler did not prevent you from doing this and you do this:
val eventListener: EventListener<Event> = HelloEventListener()
eventListener.handle(BoringEvent()) // CLASS CAST EXCEPTION AT RUN TIME!
If this were allowed your HelloEventListener would try to call sayHello() on the BoringEvent, which doesn't have that function, so it will crash. This is what generics are here to protect you from.
Now suppose your HelloEventListener.handle() didn't call event.sayHello(). Well, then it could have safely handled a BoringEvent. But the compiler isn't doing that level of analysis for you. It just knows what you declared, that HelloEventListener cannot handle anything except HelloEvent.

Kotlin generics supertype not applied

I was coding on Java for quite a long time and trying to migrate to Kotlin. I'm confused with Generics in Kotlin a bit...
I have a DelegateManager class. It should consume only subtypes of IViewData
class DelegateManager<T : IViewData> {
private val delegates: MutableList<AdapterDelegate<T>> = mutableListOf()
fun addDelegate(adapterDelegate: AdapterDelegate<T>) {
delegates.add(adapterDelegate)
}
...
}
Inside TrackListAdapter I want to add a delegate. As you might have seen it's AdapterDelegate<TrackViewData> and TrackViewData is a subtype of IViewData So it should work but it shows error inside init block of TrackListAdapter
class TrackListAdapter : BaseListAdapter<IViewData>() {
init {
delegateManager.addDelegate(TrackViewDelegate()) // error: Type mismatch -> Required: AdapterDelegate<IViewData>, Found: TrackViewDelegate
}
}
class TrackViewDelegate : AdapterDelegate<TrackViewData>() {
override fun onCreateViewHolder(parent: ViewGroup): ListViewHolder<TrackViewData> {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.track_item, parent, false)
return TrackViewHolder(itemView)
}
override fun isDelegateForDataType(data: IViewData) = data is TrackViewData
}
How to deal with it? How to extend the generic parameter correctly?