Skip to content

Commit 0fe43f5

Browse files
committed
Ensure posted events from the ViewModel are consumed (once) by the UI
Inspired from Kotlin/kotlinx.coroutines#3002
1 parent 0025e66 commit 0fe43f5

File tree

7 files changed

+77
-10
lines changed

7 files changed

+77
-10
lines changed

vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,11 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
128128
fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(
129129
observer: (T) -> Unit,
130130
) {
131+
val tag = this@VectorBaseActivity::class.simpleName.toString()
131132
lifecycleScope.launch {
132133
repeatOnLifecycle(Lifecycle.State.RESUMED) {
133-
viewEvents.stream()
134+
viewEvents
135+
.stream(tag)
134136
.collect {
135137
hideWaitingView()
136138
observer(it)

vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,11 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
205205
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(
206206
observer: (T) -> Unit,
207207
) {
208+
val tag = this@VectorBaseBottomSheetDialogFragment::class.simpleName.toString()
208209
lifecycleScope.launch {
209210
repeatOnLifecycle(Lifecycle.State.RESUMED) {
210-
viewEvents.stream()
211+
viewEvents
212+
.stream(tag)
211213
.collect {
212214
observer(it)
213215
}

vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,11 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
277277
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(
278278
observer: (T) -> Unit,
279279
) {
280+
val tag = this@VectorBaseFragment::class.simpleName.toString()
280281
lifecycleScope.launch {
281282
repeatOnLifecycle(Lifecycle.State.RESUMED) {
282-
viewEvents.stream()
283+
viewEvents
284+
.stream(tag)
283285
.collect {
284286
dismissLoadingDialog()
285287
observer(it)

vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt

+5-4
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,16 @@ package im.vector.app.core.platform
1818

1919
import com.airbnb.mvrx.MavericksState
2020
import com.airbnb.mvrx.MavericksViewModel
21-
import im.vector.app.core.utils.DataSource
22-
import im.vector.app.core.utils.PublishDataSource
21+
import im.vector.app.core.utils.EventQueue
22+
import im.vector.app.core.utils.SharedEvents
2323

2424
abstract class VectorViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(initialState: S) :
2525
MavericksViewModel<S>(initialState) {
2626

2727
// Used to post transient events to the View
28-
protected val _viewEvents = PublishDataSource<VE>()
29-
val viewEvents: DataSource<VE> = _viewEvents
28+
protected val _viewEvents = EventQueue<VE>(capacity = 64)
29+
val viewEvents: SharedEvents<VE>
30+
get() = _viewEvents
3031

3132
abstract fun handle(action: VA)
3233
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.core.utils
18+
19+
import kotlinx.coroutines.flow.Flow
20+
import kotlinx.coroutines.flow.MutableSharedFlow
21+
import kotlinx.coroutines.flow.transform
22+
import java.util.concurrent.CopyOnWriteArraySet
23+
24+
interface SharedEvents<out T> {
25+
fun stream(consumerId: String): Flow<T>
26+
}
27+
28+
class EventQueue<T>(capacity: Int) : SharedEvents<T> {
29+
30+
private val innerQueue = MutableSharedFlow<OneTimeEvent<T>>(replay = capacity)
31+
32+
fun post(event: T) {
33+
innerQueue.tryEmit(OneTimeEvent(event))
34+
}
35+
36+
override fun stream(consumerId: String): Flow<T> = innerQueue.filterNotHandledBy(consumerId)
37+
}
38+
39+
/**
40+
* Event designed to be delivered only once to a concrete entity,
41+
* but it can also be delivered to multiple different entities.
42+
*
43+
* Keeps track of who has already handled its content.
44+
*/
45+
private class OneTimeEvent<out T>(private val content: T) {
46+
47+
private val handlers = CopyOnWriteArraySet<String>()
48+
49+
/**
50+
* @param asker Used to identify, whether this "asker" has already handled this Event.
51+
* @return Event content or null if it has been already handled by asker
52+
*/
53+
fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null
54+
}
55+
56+
private fun <T> Flow<OneTimeEvent<T>>.filterNotHandledBy(consumerId: String): Flow<T> = transform { event ->
57+
event.getIfNotHandled(consumerId)?.let { emit(it) }
58+
}

vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,12 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt
239239
}
240240

241241
private fun observeViewEvents() {
242+
val tag = this::class.simpleName.toString()
242243
lifecycleScope.launch {
243244
repeatOnLifecycle(Lifecycle.State.RESUMED) {
244245
viewModel
245246
.viewEvents
246-
.stream()
247+
.stream(tag)
247248
.collect(::handleViewEvents)
248249
}
249250
}

vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,11 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
7272
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(
7373
observer: (T) -> Unit,
7474
) {
75+
val tag = this@VectorSettingsBaseFragment::class.simpleName.toString()
7576
lifecycleScope.launch {
76-
repeatOnLifecycle(state) {
7777
repeatOnLifecycle(Lifecycle.State.RESUMED) {
78-
viewEvents.stream()
78+
viewEvents
79+
.stream(tag)
7980
.collect {
8081
observer(it)
8182
}

0 commit comments

Comments
 (0)