-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider making BroadcastChannels to implement Flow #1082
Comments
What about |
Having thought about it a bit more, it looks the whole |
+1 to DataFlow Will this also mean that internal implementation of DataFlow will be changed to be better for MVVM/reactive use case (yes, I'm talking about #395) |
I've said it before and will say it again. The nice thing about The only thing I'm missing is an easy way to control the storing of the state. I have a usecase where I want to push "an |
I do think that a broadcast channel with the capacity of zero is missing too (like publish subject in RX) . |
@adibfara What is your use case for such a zero-capacity broadcast channel? |
@elizarov What I meant was, currently I found no way to have a broadcast channel (a channel that does not suspend for writing), that does not emit the value that is holding to the anyone that opens a subscription. If this seems interesting, I can move it to another thread/issue since it might be unrelated to the title of this issue. |
@adibfara Let's keep this discussion here for now because it is important to take into account for this issue of aligning broadcast channels with flows. It is important to learn more about your use-case, as it related to, at least, proper naming of these concepts. Let me try to rephase how I'm getting this particular use case for zero-capacity (not conflated): Say we have some classical callback-based even-subscription API:
This API is quite error-prone as it is easy to forget
Moreover we already have an API for that (current called
The problem that we have is back-pressure and context management. The collectors (listeners) maybe slow and busy we something else at this moment. So what should Here is how Is this one-element-channel enough? Do you really have a use-case where |
The way I currently deal with "hot flows" exposed as You can see how I implemented the I use it a lot in my projects, that is my alternative to repeating callbacks that dispatch a state or events, and it allows me to ensure proper cancellation when the state has changed. I did not use flow in its current form yet because it doesn't seem to support this kind of usages, and that's what I'm need a lot in the project I'm working on. |
@elizarov Thanks for the info. The reason I suggested moving it out of this conversation was that with my understanding, what I'm suggesting is somewhat of a hot and maybe endless stream of values, not a cold one.
How this is handled with the ConflatedBroadCastChannel? I was under the assumption that the sender, is not concerned about back-pressure and the context of the channel's listeners. Here's what I think the current APIs's, from a cold/hot standpoint, so correct me If I'm wrong: Channels: Flow: BroadCastChannel: ConflatedBroadCastChannel: Broadcast (the thing that I was talking about):
I do not need the bc.send to wait for every collector to consume it. |
To add to the
Furthermore, the In my use case I'm looking to send events to my UI that are only important if there is a subscriber when the event is fired. For instance, if I need to display a toast notification relating to a screen, but the screen is paused or obstructed, there's not reason to show that notification, and it wouldn't make sense to show it later when the screen resumes (and resubscribes) since the context is lost. Effectively I would expect the |
A correction here, despite the fact that Would not |
@Skaiver You should avoid Also, experimental coroutines are obsolete since Kotlin 1.3 because now, we have stable coroutines. About your question it'd be better suited for Kotlin's Slack or StackOverflow as I think it goes beyond the scope of this issue. |
BroadcastChannel creates an ArrayBroadcastChannel if you pass 1 or more.
…On Thu, Jun 20, 2019, 6:35 PM Tim ***@***.***> wrote:
@Skaiver <https://github.com/skaiver> Вы должны избегать GlobalScopeи
использовать, viewModelScope как показано в документе Android
<https://developer.android.com/topic/libraries/architecture/coroutines> .
Кроме того, экспериментальные сопрограммы устарели после Kotlin 1.3,
потому что теперь у нас есть *стабильные* сопрограммы.
Что касается вашего вопроса, то он лучше подошел бы для Slack или
StackOverflow Kotlin, так как я думаю, что он выходит за рамки этой
проблемы.
My question is not related to the implementation of the viewmodel as this
is just an example.
My question is how can I use a ArrayBroadcastChannel in this example
ArrayBroadcastChannel became unavailable
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#1082?email_source=notifications&email_token=ABVG6BOOIBHSYNIPUDGUNUDP3OWWPA5CNFSM4HFAKMMKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYF6MKY#issuecomment-504096299>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABVG6BO3B5CJC3NRI3VME5TP3OWWPANCNFSM4HFAKMMA>
.
|
DataFlow is a Flow analogue to ConflatedBroadcastChannel. Since Flow API is simpler than channels APIs, the implementation of DataFlow is simpler. It consumes and allocates less memory, while still providing full deadlock-freedom (even though it is not lock-free internally). Fixes #1082
DataFlow is a Flow analogue to ConflatedBroadcastChannel. Since Flow API is simpler than channels APIs, the implementation of DataFlow is simpler. It consumes and allocates less memory, while still providing full deadlock-freedom (even though it is not lock-free internally). Fixes #1082
Take a look at #1354 please. It is a proposed flow-based analogue to |
DataFlow is a Flow analogue to ConflatedBroadcastChannel. Since Flow API is simpler than channels APIs, the implementation of DataFlow is simpler. It consumes and allocates less memory, while still providing full deadlock-freedom (even though it is not lock-free internally). Fixes #1082
Let's bikeshed a name:
Note, that the other flow class like this will be most likely called |
|
I don't like StateFlow name, "an application state" looks too domain-specific for universal primitive that may be used for many use cases not only for keeping some state
A bit strange for, why Data is untyped? why is State typed in this case? |
@chris-hatton "Value" doesn't imply the value is unchanging.
The word "Data" in I also voted |
My two cents: from the current choices, I cast my vote for I'd like to contribute some more options for the sake of brainstorming, not necessarily good but maybe they'll bounce off someone and bring out something else (feel free to boo or delete of course, and sorry for the length):
|
Neither |
Hi @zach-klippenstein, For some use case that solution can be impractical, I think to use I currently use a custom implementation of observable resource to get the current value and the further updates. I cited issue #274 because we need a builder like |
Why is it required? This means that code isn't going to be reactive. |
@zach-klippenstein you are right. In this really simple example the code is not reactive var roles = setOf("admin")
fun checkAccess(role:String){
check(role in roles)
} However we want to update the |
Still not sure why this needs non-reactive access to the value. If you're firing an event to trigger the checks, you must already be subscribing to the roles flow anyway. So you could do something like: suspend fun checkAccess(role: String) {
rolesFlow.collect { roles -> check(role in roles) }
}
// and/or
fun checkAccessIn(role: String, sessionScope: CoroutineScope) {
sessionScope.launch { checkAccess(role) }
} where |
Hi @zach-klippenstein,
|
I still don't understand what part of your design prevents doing everything reactively, but even if that is a legitimate need, providing non-reactive access to data that can change is often a source of bugs later, because new code isn't forced to deal with the fact that the value can change.
Yes, you'd need to cancel the continuously-running access checking job when the session finishes. |
Hi @zach-klippenstein, I use a stock exchange price for my factory: val factoryPriceFlow: Flow<Double> = flow { TODO() } I want to use this flow to update the current price on user interface: factoryPriceFlow
.onEach { currentPrice -> updatePrice(currentPrice) }
.launchIn(myScope) Moreove I want to implement an action to buy a stock using the current price suspend fun buyStocks(quantity: Int) {
val currentPrice = factoryPriceFlow.first() // get current value
newCreditCardTransaction(currentPrice * quantity)
fetchStock(quantity)
}
I had considered a new operator to get the latest value, but I don't have any valid proposal to build an unscoped version of it. val latestFactoryPrice by factoryPriceFlow.latest() |
I think we're getting a bit into the weeds here about general architecture so maybe we should move this convo to Slack? But that makes sense. If Another approach would be to make your typealias BuyStocksAction = (quantity: Int) -> Unit
val buyStockActions: Flow<BuyStocksAction> =
factoryPriceFlow.map { currentPrice ->
{ quantity -> buyStocksAtPrice(quantity, currentPrice) }
}
private fun buyStocksAtPrice(quantity: Int, currentPrice: Double) {
…
}
// Then in your UI layer, if you're using something like RxBindings, and #1498 existed:
val buyActions = quantityView.values.withLatestFrom(stockManager.buyStockActions) { quantity, action ->
{ action(quantity) }
}
purchaseButton.clicks.withLatestFrom(buyActions) { _, buyAction -> buyAction() }
.launchIn(viewScope) But both of these approaches might seem like a lot of extra boilerplate just to avoid exposing the current price as a simple property. Are they actually better? I would argue yes, because the concept of "factory price" is inherently reactive, so the API should reflect that. The need to sample the reactive stream at a specific instant in time is really only a consequence of the view framework you're working in not being inherently reactive (e.g. Jetpack Compose will likely make this a lot nicer). And because that is a requirement of the view layer, that's the layer that should be responsible for bridging reactive and non-reactive code. |
Zooming back out from the specifics of this example, another reason that exposing "current value" as part of your API isn't ideal is composability. Smaller API surfaces are easier to compose together than larger ones. If your consumers start depending on your current value directly, then it is harder to add intermediate components between them later. Those components would need to continue to expose both a reactive stream and their current value, so there are now two parallel paths through which data is flowing (through the stream and through the chain of current values). This is just more code to write, but also means you need more tests to cover both code paths. Once you've started designing your code to be reactive first, it's much simpler to keep everything reactive until you absolutely have to – which is typically at the view layer. |
Hi @zach-klippenstein,
...then I am forced to implement a
this is a workaround on library miss, we are discussing on it, moreover it is not possible to
So I should get the
I propose you a simple exercise, please help me to understand. The current flow send price's updates: val factoryPriceFlow: Flow<Double> = flow {
while (true) {
emit(1.0)
delay(100)
emit(2.0)
delay(200)
}
} I have to implement a simple program to print the current price, like a REST service or a trivial fun main() {
val currentPrice: Double = TODO()
println("The current price is $currentPrice")
} It should print "The current price is 1.0". Thank you in advance. |
I rethink about above
Using the current API the only proposal is: val rolesFlow: Flow<Set<String>> = TODO()
override fun checkAccess(role: String) {
val roles =
runBlocking {
withTimeoutOrNull(100) {
rolesFlow.first()
}
} ?: emptySet()
check(role in roles)
} I have to consider that a @zach-klippenstein your previous post is reasonable, however if we have to expose the same value using WebSocket (reactive) and HTTP long polling (not reactive) then there is not much choices. |
StateFlow is a Flow analogue to ConflatedBroadcastChannel. Since Flow API is simpler than channels APIs, the implementation of StateFlow is simpler. It consumes and allocates less memory, while still providing full deadlock-freedom (even though it is not lock-free internally). Fixes #1082
StateFlow is a Flow analogue to ConflatedBroadcastChannel. Since Flow API is simpler than channels APIs, the implementation of StateFlow is simpler. It consumes and allocates less memory, while still providing full deadlock-freedom (even though it is not lock-free internally). Fixes #1082
StateFlow is a Flow analogue to ConflatedBroadcastChannel. Since Flow API is simpler than channels APIs, the implementation of StateFlow is simpler. It consumes and allocates less memory, while still providing full deadlock-freedom (even though it is not lock-free internally). Fixes #1082
StateFlow is a Flow analogue to ConflatedBroadcastChannel. Since Flow API is simpler than channels APIs, the implementation of StateFlow is simpler. It consumes and allocates less memory, while still providing full deadlock-freedom (even though it is not lock-free internally). Fixes #1082
I have a use case for The use case is related to RecyclerView and handling View events on it's children, and I'll be using the example of a View.OnClickListener. RecyclerView uses the ViewHolder pattern for rendering it's children; it creates a View on demand, and then "caches" it, and reuses it when that View is scrolled off the screen. When it is reused, a method is invoked that gives you a chance to bind your data to that View. One of the benefits of this is increased performance since we don't need to allocate a new View for each child every time the RecyclerView is scrolled, and we don't need to have a 1:1 of Views to children (because that could get memory intensive very quickly). A best practice while doing this is to limit the amount of allocation that occurs during a bind, and prefer to do it when the View is getting created (or not even allocate then if possible). This often comes into play with setting OnClickListener on a View. Another issue is that it is difficult to easily pass these events up to a Presenter (or other architecture component) without a lot of manual wiring because of how RecyclerView works. My solution uses a global OnClickListener. The back end of it looks like this:
When I want to handle a click from a RecyclerView child, in my Presenter I maintain a reference to a
I can consume the clicks from my Presenter by simply:
The problem is, that if there's a misbehaving consumer that doesn't properly consume the events from If I could use Will |
After much debugging, it turns out that my issue was that I was receiving from the BroadcastChannel's ReceiveChannel using a for loop. Using consumeEach seems to have fixed the problem. I think that my above points still stand, even though it wasn't my current issue. |
@eygraber Unfortunately, I don't know a good solution to the problem you've described. That is, if you have potentially misbehaving listeners then you have to choose between:
Arguably, the latter option might be preferred for mobile apps that would not live long enough to get their memory exhausted, so it does make sense to support unlimited-size buffers, indeed. |
Is not allowing UNLIMITED already set in stone, or could it still be implemented? Similarly, is BroadcastChannel for sure on track to get deprecated? |
We do plan to provide some sort of unlimited-buffered primitive of this kind. |
I have seen the DataFlow API is marked as draft, is it going to be available soon or I should use ConflatedBroadcastChannels? |
|
Thanks a lot for a fruitful discussion in this issue and tons of useful feedback. This issue was originally created to "Consider making BroadcastChannels to implement Flow" and as a result of this feedback and discussions the decision is not to implement
I'm closing this one. |
Unlike point-to-point channels that are somewhat tricky to use, various kinds of
BroadcastChannel
implementations seem well suited to directly implementFlow
interface. That would make easier and slightly more efficient to use them as "data model" classes in MVVM architectures, being able to directly use full set of flow operators to transform them and wire to UI.Also, we might consider a shorter name for
ConflatedBroadcastChannel
that would focus on its "dataflow variable" nature.DataFlow
could be a good starting point for this name bike-shedding.The text was updated successfully, but these errors were encountered: