fix: 事件流被重复 collect

This commit is contained in:
HuanCheng65 2023-03-11 23:52:15 +08:00
parent 055e637681
commit 4673044663
No known key found for this signature in database
GPG Key ID: E9031EF91A805148
6 changed files with 121 additions and 115 deletions

View File

@ -1,9 +1,12 @@
package com.huanchengfly.tieba.post.arch package com.huanchengfly.tieba.post.arch
import android.util.Log
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -11,11 +14,11 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@ -53,18 +56,26 @@ fun <T : UiState, A> Flow<T>.collectPartialAsState(
} }
} }
inline fun <reified Event : UiEvent> CoroutineScope.onEvent( @Composable
viewModel: BaseViewModel<*, *, *, *>, inline fun <reified Event : UiEvent> BaseViewModel<*, *, *, *>.onEvent(
noinline listener: suspend (Event) -> Unit noinline listener: suspend (Event) -> Unit
) { ) {
launch { val coroutineScope = rememberCoroutineScope()
viewModel.uiEventFlow DisposableEffect(key1 = listener, key2 = this) {
.filterIsInstance<Event>() with(coroutineScope) {
.collect { val job = launch {
launch { uiEventFlow
listener(it) .filterIsInstance<Event>()
} .cancellable()
.collect {
launch {
listener(it)
}
}
} }
onDispose { job.cancel() }
}
} }
} }
@ -73,10 +84,24 @@ inline fun <reified VM : BaseViewModel<*, *, *, *>> pageViewModel(): VM {
return hiltViewModel<VM>().apply { return hiltViewModel<VM>().apply {
val context = LocalContext.current val context = LocalContext.current
if (context is BaseComposeActivity) { if (context is BaseComposeActivity) {
uiEventFlow.filterIsInstance<CommonUiEvent>() val coroutineScope = rememberCoroutineScope()
.collectIn(context) {
context.handleCommonEvent(it) DisposableEffect(key1 = this) {
with(coroutineScope) {
val job =
uiEventFlow
.filterIsInstance<CommonUiEvent>()
.cancellable()
.collectIn(context) {
context.handleCommonEvent(it)
}
onDispose {
Log.i("pageViewModel", "onDispose")
job.cancel()
}
} }
}
} }
} }
} }
@ -85,14 +110,7 @@ inline fun <reified VM : BaseViewModel<*, *, *, *>> pageViewModel(): VM {
inline fun <INTENT : UiIntent, reified VM : BaseViewModel<INTENT, *, *, *>> pageViewModel( inline fun <INTENT : UiIntent, reified VM : BaseViewModel<INTENT, *, *, *>> pageViewModel(
initialIntent: List<INTENT> = emptyList(), initialIntent: List<INTENT> = emptyList(),
): VM { ): VM {
return hiltViewModel<VM>().apply { return pageViewModel<VM>().apply {
val context = LocalContext.current
if (context is BaseComposeActivity) {
uiEventFlow.filterIsInstance<CommonUiEvent>()
.collectIn(context) {
context.handleCommonEvent(it)
}
}
if (initialIntent.isNotEmpty()) { if (initialIntent.isNotEmpty()) {
LaunchedEffect(key1 = initialized) { LaunchedEffect(key1 = initialized) {
if (!initialized) { if (!initialized) {

View File

@ -367,55 +367,53 @@ fun ForumPage(
val scaffoldState = rememberScaffoldState() val scaffoldState = rememberScaffoldState()
val snackbarHostState = scaffoldState.snackbarHostState val snackbarHostState = scaffoldState.snackbarHostState
LaunchedEffect(null) { viewModel.onEvent<ForumUiEvent.SignIn.Success> {
onEvent<ForumUiEvent.SignIn.Success>(viewModel) { snackbarHostState.showSnackbar(
snackbarHostState.showSnackbar( message = context.getString(
message = context.getString( R.string.toast_sign_success,
R.string.toast_sign_success, "${it.signBonusPoint}",
"${it.signBonusPoint}", "${it.userSignRank}"
"${it.userSignRank}"
)
) )
} )
onEvent<ForumUiEvent.SignIn.Failure>(viewModel) { }
snackbarHostState.showSnackbar( viewModel.onEvent<ForumUiEvent.SignIn.Failure> {
message = context.getString( snackbarHostState.showSnackbar(
R.string.toast_sign_failed, message = context.getString(
it.errorMsg R.string.toast_sign_failed,
) it.errorMsg
) )
} )
onEvent<ForumUiEvent.Like.Success>(viewModel) { }
snackbarHostState.showSnackbar( viewModel.onEvent<ForumUiEvent.Like.Success> {
message = context.getString( snackbarHostState.showSnackbar(
R.string.toast_like_success, message = context.getString(
it.memberSum, R.string.toast_like_success,
) it.memberSum,
) )
} )
onEvent<ForumUiEvent.Like.Failure>(viewModel) { }
snackbarHostState.showSnackbar( viewModel.onEvent<ForumUiEvent.Like.Failure> {
message = context.getString( snackbarHostState.showSnackbar(
R.string.toast_like_failed, message = context.getString(
it.errorMsg R.string.toast_like_failed,
) it.errorMsg
) )
} )
onEvent<ForumUiEvent.Unlike.Success>(viewModel) { }
snackbarHostState.showSnackbar( viewModel.onEvent<ForumUiEvent.Unlike.Success> {
message = context.getString( snackbarHostState.showSnackbar(
R.string.toast_unlike_success message = context.getString(
) R.string.toast_unlike_success
) )
} )
onEvent<ForumUiEvent.Unlike.Failure>(viewModel) { }
snackbarHostState.showSnackbar( viewModel.onEvent<ForumUiEvent.Unlike.Failure> {
message = context.getString( snackbarHostState.showSnackbar(
R.string.toast_unlike_failed, message = context.getString(
it.errorMsg R.string.toast_unlike_failed,
) it.errorMsg
) )
} )
} }
val isLoading by viewModel.uiState.collectPartialAsState( val isLoading by viewModel.uiState.collectPartialAsState(

View File

@ -132,26 +132,24 @@ fun ForumThreadListPage(
} }
} }
} }
LaunchedEffect(null) { viewModel.onEvent<ForumThreadListUiEvent.AgreeFail> {
onEvent<ForumThreadListUiEvent.AgreeFail>(viewModel) { val snackbarResult = snackbarHostState.showSnackbar(
val snackbarResult = snackbarHostState.showSnackbar( message = context.getString(
message = context.getString( R.string.snackbar_agree_fail,
R.string.snackbar_agree_fail, it.errorCode,
it.errorCode, it.errorMsg
it.errorMsg ),
), actionLabel = context.getString(R.string.button_retry)
actionLabel = context.getString(R.string.button_retry) )
)
if (snackbarResult == SnackbarResult.ActionPerformed) { if (snackbarResult == SnackbarResult.ActionPerformed) {
viewModel.send( viewModel.send(
ForumThreadListUiIntent.Agree( ForumThreadListUiIntent.Agree(
it.threadId, it.threadId,
it.postId, it.postId,
it.hasAgree it.hasAgree
)
) )
} )
} }
} }
val isRefreshing by viewModel.uiState.collectPartialAsState( val isRefreshing by viewModel.uiState.collectPartialAsState(

View File

@ -91,18 +91,16 @@ fun HistoryListPage(
val context = LocalContext.current val context = LocalContext.current
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
LaunchedEffect(null) { viewModel.onEvent<HistoryListUiEvent.Delete.Failure> {
onEvent<HistoryListUiEvent.Delete.Failure>(viewModel) { snackbarHostState.showSnackbar(
snackbarHostState.showSnackbar( context.getString(
context.getString( R.string.delete_history_failure,
R.string.delete_history_failure, it.errorMsg
it.errorMsg
)
) )
} )
onEvent<HistoryListUiEvent.Delete.Success>(viewModel) { }
snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success)) viewModel.onEvent<HistoryListUiEvent.Delete.Success> {
} snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success))
} }
Box( Box(
modifier = Modifier modifier = Modifier

View File

@ -24,7 +24,6 @@ import androidx.compose.material.icons.outlined.AccountCircle
import androidx.compose.material.icons.outlined.Block import androidx.compose.material.icons.outlined.Block
import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -185,15 +184,13 @@ fun BlockSettingsPage(
} }
) { paddingValues -> ) { paddingValues ->
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
LaunchedEffect(null) { viewModel.onEvent<BlockSettingsUiEvent.Success> {
onEvent<BlockSettingsUiEvent.Success>(viewModel) { snackbarHostState.showSnackbar(
snackbarHostState.showSnackbar( when (it) {
when (it) { is BlockSettingsUiEvent.Success.Add -> context.getString(R.string.toast_add_success)
is BlockSettingsUiEvent.Success.Add -> context.getString(R.string.toast_add_success) is BlockSettingsUiEvent.Success.Delete -> context.getString(R.string.toast_delete_success)
is BlockSettingsUiEvent.Success.Delete -> context.getString(R.string.toast_delete_success) }
} )
)
}
} }
HorizontalPager( HorizontalPager(
count = 2, count = 2,

View File

@ -20,7 +20,6 @@ import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material.rememberScaffoldState import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -107,18 +106,16 @@ fun ThreadStorePage(
val pullRefreshState = rememberPullRefreshState( val pullRefreshState = rememberPullRefreshState(
refreshing = isRefreshing, refreshing = isRefreshing,
onRefresh = { viewModel.send(ThreadStoreUiIntent.Refresh) }) onRefresh = { viewModel.send(ThreadStoreUiIntent.Refresh) })
LaunchedEffect(null) { viewModel.onEvent<ThreadStoreUiEvent.Delete.Failure> {
onEvent<ThreadStoreUiEvent.Delete.Failure>(viewModel) { scaffoldState.snackbarHostState.showSnackbar(
scaffoldState.snackbarHostState.showSnackbar( context.getString(
context.getString( R.string.delete_store_failure,
R.string.delete_store_failure, it.errorMsg
it.errorMsg
)
) )
} )
onEvent<ThreadStoreUiEvent.Delete.Success>(viewModel) { }
scaffoldState.snackbarHostState.showSnackbar(context.getString(R.string.delete_store_success)) viewModel.onEvent<ThreadStoreUiEvent.Delete.Success> {
} scaffoldState.snackbarHostState.showSnackbar(context.getString(R.string.delete_store_success))
} }
MyScaffold( MyScaffold(
backgroundColor = Color.Transparent, backgroundColor = Color.Transparent,