If i have a SharedFlow of sealed class, can i subs...
# coroutines
g
If i have a SharedFlow of sealed class, can i subscribe to just specific event? If so how?
h
Use
filterIsInstance
,
mapNotNull
+
when
or
filter
+
when
, but I would not use
filterIsInstance
with sealed classes to not forget to update the check when adding new cases.
g
I tried adding generic function to my class, but due to type erasue I can't forward the type to sharedflow
Copy code
fun <Event: AppEventData> subscribeForSpecificEvent(scope: CoroutineScope,  block: suspend (AppEventData) -> Unit) =
        _eventFlow.mapNotNull { if (it is Event) it else null}
Cannot check for instance due to type erasure
g
Could you show some self contained example It doesn’t look as something Flow specific, after all Flow callback is not different from any another lambda used as callback
g
Copy code
sealed class AppEvent {
    data class Event1(val name: String) : AppEvent()
    data class OtherEvent(val id: Int, val data: Long) : AppEvent()
}

class AppEventBus {
    private val _eventFlow = MutableSharedFlow<AppEvent>()

    fun subscribe(scope: CoroutineScope, block: suspend (AppEvent) -> Unit) = _eventFlow.onEach(block).launchIn(scope)

    suspend fun emit(appEvent: AppEvent) = _eventFlow.emit(appEvent)
}
Let's say i have something like this
Now everyone can subscribe to ALL events. And respond only to the event it's actually interested on. But I do not want to notify listeners about irrelevant events. Let's say someone is interested in only Evet1 events. How would one subscribe to specific event types.
g
in general, filter is way to go
Why would you create AppEventBus instead of using Flow, it looks as a huge step backward
g
I Am using Flow
g
I also don’t see any erasure in your case
g
Because I deleted it.
g
I Am using Flow
Well, no, you hide it behind far inferior callback based API
You deleted erasure? In general, I don’t see anything Flow specific in this case, yes, erasure can be an issue if you have some generic events, but it would be an issue without flow too
g
fun <Event:AppEvent> subscribeForEvent(scope: CoroutineScope, block: suspend (AppEventData) -> Unit) = _eventFlow.filter { /*TYPE ERASURE :(*/ )}
g
Yes, pass class as argument
+ use inline to avoid erasure
g
Can you show me example? I don't get it
g
Yes, give me a minute
It was only the showcase for the signature
g
Ahh, right
g
inline fun <reified Event:AppEvent> subscribeFor(scope: CoroutineScope, block: suspend (AppEventData) -> Unit) : Flow<Event> = _eventFlow.filterIsInstance<Event>().onEach(block).launchIn(scope) Suspend inline lambda parameters of non-suspend functions are not supported
g
Copy code
inline fun <reified Event : AppEvent> subscribeForEvent(
            scope: CoroutineScope,
            noinline block: suspend (AppEvent) -> Unit
    ) = subscribe(scope) { if (it is Event) block(it) }
h
then make subscribeFor suspend too or noinline
g
You should mark it as noinline
Making it suspend doesn’t really work for this API, it should be non suspend subscription
h
Oh, yeah in this context it is useless
g
only type parameters of inline functions can be reified
can't mark it noinline
g
Well, this function is inline, see above
just insert it into your class, it works for your current implementation
g
This compiles: inline fun <reified Event:AppEvent> subscribeFor(scope: CoroutineScope, noinline block: suspend (Event) -> Unit) = events.filterIsInstance<Event>().onEach(block).launchIn(scope)
g
ahh, right, I see now
g
The only last problem: I Hate it 🙂 It looks like over engineering. Do you have a good example of how I can pass events from various classes to other classes
g
No issue
Back to my original suggestion, by splitting it up
Copy code
fun <Event : AppEvent> subscribeForEvent(
        scope: CoroutineScope,
        eventType: Class<Event>,
        block: suspend (Event) -> Unit
) = subscribe(scope) {
    if (eventType.isInstance(it)) {
        @Suppress("UNCHECKED_CAST")
        block(it as Event)
    }
}
So now you filter it by class, and on top it add inline version:
Copy code
inline fun <reified Event : AppEvent> subscribeForEvent(
            scope: CoroutineScope,
            noinline block: suspend (Event) -> Unit
    ) = subscribeForEvent(scope, Event::class.java, block)
Wait, no, I was confused, having single method also works, no issue (just fixed type of lambda from my previous example):
Copy code
inline fun <reified Event : AppEvent> subscribeForEvent(
        scope: CoroutineScope,
        noinline block: suspend (Event) -> Unit
) = subscribe(scope) { if (it is Event) block(it) }
The only last problem: I Hate it 🙂
It looks like over engineering.
Do you have a good example of how I can pass events from various classes to other classes
What is over engineering?
You pass class explicitly as param, or you use inline class to do this for you, I really don’t see issue with it
g
What is over engineering? A Solution more complex than a problem 😉
g
Nice to mock me, even if I try to help
I don’t see it over engineering at all, it’s just usage of Kotlin reified types
g
What? I was not talking about you
About me
I tend to take simple problems and come up with solutions more complex
g
reified types used in a lot of Kotlin stdlib functions and in our code too, it’s just a way to avoid passing explicit class
> Do you have a good example of how I can pass events from various classes to other classes
I feel that this question not about rified types, right? But about EventBus
Then yes, as I said above, I feel that downgrade stream from flow to event bus with callbacks is bad solution
g
Right. I Mean Ii have a niche class, somewhere deep deep in the classes tree. It needs sensor data, rarely. it wants to ask the data from provider, and be notified about events. But i do not wish to pass this data all the way down. So my question is how the data flow is usually done in android apps.
g
pass events from various classes to other classes
Yes, create a class with Flow (which can be implemented in any kind of flow), inject it where you need events
so yeah, as you said, you have to pass it all way down in this case (manually or automatically with DI library)
g
Ok, thanks a lot.
g
EventBus also should be injected somehow, so I don’t see big difference in this case
In extreme case, well, you can use global property after all to access this repository of events (not that I would suggest it, but at least it will be properly typed and will have only one use case, unlike event bus which delivers all events for everyone)