From e865ffafde2fe8594acb3541bc0b0336c5b1d425 Mon Sep 17 00:00:00 2001 From: Zongle Wang Date: Sat, 30 Mar 2024 03:02:12 +0800 Subject: [PATCH] Don't use mutable shared flows in UI (#4346) --- .../keylesspalace/tusky/appstore/EventsHub.kt | 6 +++--- .../account/list/ListsForAccountViewModel.kt | 7 ++++--- .../components/compose/ComposeViewModel.kt | 15 ++++++------- .../domainblocks/DomainBlocksViewModel.kt | 11 ++++++---- .../components/filters/FiltersViewModel.kt | 5 +++-- .../viewthread/ViewThreadViewModel.kt | 16 +++++++------- .../tusky/viewmodel/ListsViewModel.kt | 21 +++++++++++-------- 7 files changed, 45 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/appstore/EventsHub.kt b/app/src/main/java/com/keylesspalace/tusky/appstore/EventsHub.kt index 05bd4542c..88634681a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/appstore/EventsHub.kt +++ b/app/src/main/java/com/keylesspalace/tusky/appstore/EventsHub.kt @@ -15,11 +15,11 @@ interface Event @Singleton class EventHub @Inject constructor() { - private val sharedEventFlow = MutableSharedFlow() - val events: SharedFlow = sharedEventFlow.asSharedFlow() + private val _events = MutableSharedFlow() + val events: SharedFlow = _events.asSharedFlow() suspend fun dispatch(event: Event) { - sharedEventFlow.emit(event) + _events.emit(event) } // TODO remove as soon as NotificationsFragment is Kotlin diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/list/ListsForAccountViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/list/ListsForAccountViewModel.kt index 5113b9fe0..b0546eceb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/list/ListsForAccountViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/list/ListsForAccountViewModel.kt @@ -28,6 +28,7 @@ import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -53,13 +54,13 @@ class ListsForAccountViewModel @Inject constructor( ) : ViewModel() { private val _states = MutableSharedFlow>(1) - val states: SharedFlow> = _states + val states: SharedFlow> = _states.asSharedFlow() private val _loadError = MutableSharedFlow(1) - val loadError: SharedFlow = _loadError + val loadError: SharedFlow = _loadError.asSharedFlow() private val _actionError = MutableSharedFlow(1) - val actionError: SharedFlow = _actionError + val actionError: SharedFlow = _actionError.asSharedFlow() fun load(accountId: String?) { _loadError.resetReplayCache() diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 82b2f3583..99224825d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.shareIn @@ -107,12 +108,12 @@ class ComposeViewModel @Inject constructor( private val _media = MutableStateFlow(emptyList()) val media: StateFlow> = _media.asStateFlow() - val uploadError = - MutableSharedFlow( - replay = 0, - extraBufferCapacity = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) + private val _uploadError = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val uploadError: SharedFlow = _uploadError.asSharedFlow() private val _closeConfirmation = MutableStateFlow(ConfirmationKind.NONE) val closeConfirmation: StateFlow = _closeConfirmation.asStateFlow() @@ -202,7 +203,7 @@ class ComposeViewModel @Inject constructor( ) is UploadEvent.ErrorEvent -> { _media.update { mediaList -> mediaList.filter { it.localId != mediaItem.localId } } - uploadError.emit(event.error) + _uploadError.emit(event.error) return@collect } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt index 04f4f426d..aa316c651 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt @@ -10,6 +10,8 @@ import at.connyduck.calladapter.networkresult.onFailure import com.keylesspalace.tusky.R import javax.inject.Inject import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch class DomainBlocksViewModel @Inject constructor( @@ -18,12 +20,13 @@ class DomainBlocksViewModel @Inject constructor( val domainPager = repo.domainPager.cachedIn(viewModelScope) - val uiEvents = MutableSharedFlow() + private val _uiEvents = MutableSharedFlow() + val uiEvents: SharedFlow = _uiEvents.asSharedFlow() fun block(domain: String) { viewModelScope.launch { repo.block(domain).onFailure { e -> - uiEvents.emit( + _uiEvents.emit( SnackbarEvent( message = R.string.error_blocking_domain, domain = domain, @@ -39,7 +42,7 @@ class DomainBlocksViewModel @Inject constructor( fun unblock(domain: String) { viewModelScope.launch { repo.unblock(domain).fold({ - uiEvents.emit( + _uiEvents.emit( SnackbarEvent( message = R.string.confirmation_domain_unmuted, domain = domain, @@ -49,7 +52,7 @@ class DomainBlocksViewModel @Inject constructor( ) ) }, { e -> - uiEvents.emit( + _uiEvents.emit( SnackbarEvent( message = R.string.error_unblocking_domain, domain = domain, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersViewModel.kt index 315af81db..0f1a5a2ac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersViewModel.kt @@ -11,8 +11,9 @@ import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.isHttpNotFound import javax.inject.Inject -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class FiltersViewModel @Inject constructor( @@ -30,8 +31,8 @@ class FiltersViewModel @Inject constructor( data class State(val filters: List, val loadingState: LoadingState) - val state: Flow get() = _state private val _state = MutableStateFlow(State(emptyList(), LoadingState.INITIAL)) + val state: StateFlow = _state.asStateFlow() fun load() { this@FiltersViewModel._state.value = _state.value.copy(loadingState = LoadingState.LOADING) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt index 5ed9b08b4..afde9ebfb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt @@ -51,6 +51,8 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -68,14 +70,12 @@ class ViewThreadViewModel @Inject constructor( private val _uiState = MutableStateFlow(ThreadUiState.Loading as ThreadUiState) val uiState: Flow = _uiState.asStateFlow() - private val _errors = - MutableSharedFlow( - replay = 0, - extraBufferCapacity = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) - val errors: Flow - get() = _errors + private val _errors = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val errors: SharedFlow = _errors.asSharedFlow() var isInitialLoad: Boolean = true diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/ListsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/ListsViewModel.kt index 500500328..d9fdbd590 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/ListsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/ListsViewModel.kt @@ -27,9 +27,12 @@ import java.io.IOException import java.net.ConnectException import javax.inject.Inject import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch internal class ListsViewModel @Inject constructor(private val api: MastodonApi) : ViewModel() { @@ -49,15 +52,15 @@ internal class ListsViewModel @Inject constructor(private val api: MastodonApi) data class State(val lists: List, val loadingState: LoadingState) - val state: Flow get() = _state - val events: Flow get() = _events private val _state = MutableStateFlow(State(listOf(), LoadingState.INITIAL)) - private val _events = - MutableSharedFlow( - replay = 0, - extraBufferCapacity = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) + val state: StateFlow = _state.asStateFlow() + + private val _events = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val events: SharedFlow = _events.asSharedFlow() fun retryLoading() { loadIfNeeded()