Don't use mutable shared flows in UI (#4346)

This commit is contained in:
Zongle Wang 2024-03-30 03:02:12 +08:00 committed by GitHub
parent 06f283575d
commit e865ffafde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 45 additions and 36 deletions

View File

@ -15,11 +15,11 @@ interface Event
@Singleton @Singleton
class EventHub @Inject constructor() { class EventHub @Inject constructor() {
private val sharedEventFlow = MutableSharedFlow<Event>() private val _events = MutableSharedFlow<Event>()
val events: SharedFlow<Event> = sharedEventFlow.asSharedFlow() val events: SharedFlow<Event> = _events.asSharedFlow()
suspend fun dispatch(event: Event) { suspend fun dispatch(event: Event) {
sharedEventFlow.emit(event) _events.emit(event)
} }
// TODO remove as soon as NotificationsFragment is Kotlin // TODO remove as soon as NotificationsFragment is Kotlin

View File

@ -28,6 +28,7 @@ import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -53,13 +54,13 @@ class ListsForAccountViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
private val _states = MutableSharedFlow<List<AccountListState>>(1) private val _states = MutableSharedFlow<List<AccountListState>>(1)
val states: SharedFlow<List<AccountListState>> = _states val states: SharedFlow<List<AccountListState>> = _states.asSharedFlow()
private val _loadError = MutableSharedFlow<Throwable>(1) private val _loadError = MutableSharedFlow<Throwable>(1)
val loadError: SharedFlow<Throwable> = _loadError val loadError: SharedFlow<Throwable> = _loadError.asSharedFlow()
private val _actionError = MutableSharedFlow<ActionError>(1) private val _actionError = MutableSharedFlow<ActionError>(1)
val actionError: SharedFlow<ActionError> = _actionError val actionError: SharedFlow<ActionError> = _actionError.asSharedFlow()
fun load(accountId: String?) { fun load(accountId: String?) {
_loadError.resetReplayCache() _loadError.resetReplayCache()

View File

@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.shareIn
@ -107,12 +108,12 @@ class ComposeViewModel @Inject constructor(
private val _media = MutableStateFlow(emptyList<QueuedMedia>()) private val _media = MutableStateFlow(emptyList<QueuedMedia>())
val media: StateFlow<List<QueuedMedia>> = _media.asStateFlow() val media: StateFlow<List<QueuedMedia>> = _media.asStateFlow()
val uploadError = private val _uploadError = MutableSharedFlow<Throwable>(
MutableSharedFlow<Throwable>( replay = 0,
replay = 0, extraBufferCapacity = 1,
extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
onBufferOverflow = BufferOverflow.DROP_OLDEST )
) val uploadError: SharedFlow<Throwable> = _uploadError.asSharedFlow()
private val _closeConfirmation = MutableStateFlow(ConfirmationKind.NONE) private val _closeConfirmation = MutableStateFlow(ConfirmationKind.NONE)
val closeConfirmation: StateFlow<ConfirmationKind> = _closeConfirmation.asStateFlow() val closeConfirmation: StateFlow<ConfirmationKind> = _closeConfirmation.asStateFlow()
@ -202,7 +203,7 @@ class ComposeViewModel @Inject constructor(
) )
is UploadEvent.ErrorEvent -> { is UploadEvent.ErrorEvent -> {
_media.update { mediaList -> mediaList.filter { it.localId != mediaItem.localId } } _media.update { mediaList -> mediaList.filter { it.localId != mediaItem.localId } }
uploadError.emit(event.error) _uploadError.emit(event.error)
return@collect return@collect
} }
} }

View File

@ -10,6 +10,8 @@ import at.connyduck.calladapter.networkresult.onFailure
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class DomainBlocksViewModel @Inject constructor( class DomainBlocksViewModel @Inject constructor(
@ -18,12 +20,13 @@ class DomainBlocksViewModel @Inject constructor(
val domainPager = repo.domainPager.cachedIn(viewModelScope) val domainPager = repo.domainPager.cachedIn(viewModelScope)
val uiEvents = MutableSharedFlow<SnackbarEvent>() private val _uiEvents = MutableSharedFlow<SnackbarEvent>()
val uiEvents: SharedFlow<SnackbarEvent> = _uiEvents.asSharedFlow()
fun block(domain: String) { fun block(domain: String) {
viewModelScope.launch { viewModelScope.launch {
repo.block(domain).onFailure { e -> repo.block(domain).onFailure { e ->
uiEvents.emit( _uiEvents.emit(
SnackbarEvent( SnackbarEvent(
message = R.string.error_blocking_domain, message = R.string.error_blocking_domain,
domain = domain, domain = domain,
@ -39,7 +42,7 @@ class DomainBlocksViewModel @Inject constructor(
fun unblock(domain: String) { fun unblock(domain: String) {
viewModelScope.launch { viewModelScope.launch {
repo.unblock(domain).fold({ repo.unblock(domain).fold({
uiEvents.emit( _uiEvents.emit(
SnackbarEvent( SnackbarEvent(
message = R.string.confirmation_domain_unmuted, message = R.string.confirmation_domain_unmuted,
domain = domain, domain = domain,
@ -49,7 +52,7 @@ class DomainBlocksViewModel @Inject constructor(
) )
) )
}, { e -> }, { e ->
uiEvents.emit( _uiEvents.emit(
SnackbarEvent( SnackbarEvent(
message = R.string.error_unblocking_domain, message = R.string.error_unblocking_domain,
domain = domain, domain = domain,

View File

@ -11,8 +11,9 @@ import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.isHttpNotFound import com.keylesspalace.tusky.util.isHttpNotFound
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class FiltersViewModel @Inject constructor( class FiltersViewModel @Inject constructor(
@ -30,8 +31,8 @@ class FiltersViewModel @Inject constructor(
data class State(val filters: List<Filter>, val loadingState: LoadingState) data class State(val filters: List<Filter>, val loadingState: LoadingState)
val state: Flow<State> get() = _state
private val _state = MutableStateFlow(State(emptyList(), LoadingState.INITIAL)) private val _state = MutableStateFlow(State(emptyList(), LoadingState.INITIAL))
val state: StateFlow<State> = _state.asStateFlow()
fun load() { fun load() {
this@FiltersViewModel._state.value = _state.value.copy(loadingState = LoadingState.LOADING) this@FiltersViewModel._state.value = _state.value.copy(loadingState = LoadingState.LOADING)

View File

@ -51,6 +51,8 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -68,14 +70,12 @@ class ViewThreadViewModel @Inject constructor(
private val _uiState = MutableStateFlow(ThreadUiState.Loading as ThreadUiState) private val _uiState = MutableStateFlow(ThreadUiState.Loading as ThreadUiState)
val uiState: Flow<ThreadUiState> = _uiState.asStateFlow() val uiState: Flow<ThreadUiState> = _uiState.asStateFlow()
private val _errors = private val _errors = MutableSharedFlow<Throwable>(
MutableSharedFlow<Throwable>( replay = 0,
replay = 0, extraBufferCapacity = 1,
extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
onBufferOverflow = BufferOverflow.DROP_OLDEST )
) val errors: SharedFlow<Throwable> = _errors.asSharedFlow()
val errors: Flow<Throwable>
get() = _errors
var isInitialLoad: Boolean = true var isInitialLoad: Boolean = true

View File

@ -27,9 +27,12 @@ import java.io.IOException
import java.net.ConnectException import java.net.ConnectException
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow 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 import kotlinx.coroutines.launch
internal class ListsViewModel @Inject constructor(private val api: MastodonApi) : ViewModel() { 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<MastoList>, val loadingState: LoadingState) data class State(val lists: List<MastoList>, val loadingState: LoadingState)
val state: Flow<State> get() = _state
val events: Flow<Event> get() = _events
private val _state = MutableStateFlow(State(listOf(), LoadingState.INITIAL)) private val _state = MutableStateFlow(State(listOf(), LoadingState.INITIAL))
private val _events = val state: StateFlow<State> = _state.asStateFlow()
MutableSharedFlow<Event>(
replay = 0, private val _events = MutableSharedFlow<Event>(
extraBufferCapacity = 1, replay = 0,
onBufferOverflow = BufferOverflow.DROP_OLDEST extraBufferCapacity = 1,
) onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val events: SharedFlow<Event> = _events.asSharedFlow()
fun retryLoading() { fun retryLoading() {
loadIfNeeded() loadIfNeeded()