I have this scenario where I have a super abstract class that emits different types of events using Kotlin sealed classes
.
These events are modeled as follows.
sealed class BaseEvent {
object ConnectionStarted : BaseEvent()
object ConnectionStopped : BaseEvent()
}
sealed class LegacyEvent : BaseEvent() {
object TextChanged : LegacyEvent()
object TextCleared : LegacyEvent()
}
sealed class AdvancedEvent : BaseEvent() {
object ButtonClick : AdvancedEvent()
object ButtonLongClick : AdvancedEvent()
}
And here are the classes that emit these events
abstract class BaseViewModel<E : BaseEvent> {
private fun startConnection() {
emit(BaseEvent.ConnectionStarted) // <-- Error
}
fun emit(event: E){
//...
}
}
class LegacyBaskan : BaseViewModel<LegacyEvent>() {
fun textChanged() {
emit(LegacyEvent.TextChanged) // <-- Works
}
}
class AdvancedBaskan : BaseViewModel<AdvancedEvent>() {
fun buttonClicked() {
emit(AdvancedEvent.ButtonClick) // <-- Works
}
}
Here, it only works for the subclass and I can emit any event in the LegacyEvent
or AdvancedEvent
in their associated classes. However, for the BaseBaskan
class, I can't emit the events from the BaseEvent
although I stated that the generic type E
must extend the BaseEvent
.
I need each subclass to have access to its own events as well as the superclass events, but not the other subclasses' events.
How can I still emit events from BaseEvent
in the base class, while giving each class the access to emit its own events only?
CodePudding user response:
Not sure if you're confused about why it's not letting you emit the item from the base class. Since E could be any subtype of BaseEvent, if your class could emit ConnectionStarted
, then it would be violating its contract any time it is declared as a BaseViewModel<AnythingBesidesConnectionStarted>
.
Only way I can think of to make this work is have both private and public versions of the emit
function. You might have to change code elsewhere in your class that you haven't shown. If there's some function that returns E
, you will have to change it so it returns BaseEvent
.
abstract class BaseViewModel<E : BaseEvent> {
private fun startConnection() {
emitInternal(BaseEvent.ConnectionStarted)
}
private fun emitInternal(event: BaseEvent) {
//...
}
fun emit(event: E){
emitInternal(event)
}
}
CodePudding user response:
You can't emit BaseEvent.ConnectionStarted
in BaseViewModel
(and other events as well) because E
is not defined yet, so the type system can't be sure that you won't emit events of another subtype breaking generic type invariance.
Just add an overloaded private version, which accepts BaseEvent
argument (you'll need some @JvmName
annotation to make it compilable for JVM target):
abstract class BaseViewModel<E : BaseEvent> {
private fun startConnection() {
emit(BaseEvent.ConnectionStarted)
}
@JvmName("emitBaseEvent")
private fun emit(event: BaseEvent) {
//...
}
fun emit(event: E) {
emit(event as BaseEvent)
}
}
CodePudding user response:
It looks like you need contravariance, which can be achieved using in
. Assuming your base class only has methods such as emit
that use type E
as parameter type, not as return type, then:
abstract class BaseViewModel<in E : BaseEvent> {
See https://kotlinlang.org/docs/generics.html#use-site-variance-type-projections.