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,19 +56,27 @@ 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) {
with(coroutineScope) {
val job = launch {
uiEventFlow
.filterIsInstance<Event>() .filterIsInstance<Event>()
.cancellable()
.collect { .collect {
launch { launch {
listener(it) listener(it)
} }
} }
} }
onDispose { job.cancel() }
}
}
} }
@Composable @Composable
@ -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()
DisposableEffect(key1 = this) {
with(coroutineScope) {
val job =
uiEventFlow
.filterIsInstance<CommonUiEvent>()
.cancellable()
.collectIn(context) { .collectIn(context) {
context.handleCommonEvent(it) 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,8 +367,7 @@ 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,
@ -377,7 +376,7 @@ fun ForumPage(
) )
) )
} }
onEvent<ForumUiEvent.SignIn.Failure>(viewModel) { viewModel.onEvent<ForumUiEvent.SignIn.Failure> {
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = context.getString( message = context.getString(
R.string.toast_sign_failed, R.string.toast_sign_failed,
@ -385,7 +384,7 @@ fun ForumPage(
) )
) )
} }
onEvent<ForumUiEvent.Like.Success>(viewModel) { viewModel.onEvent<ForumUiEvent.Like.Success> {
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = context.getString( message = context.getString(
R.string.toast_like_success, R.string.toast_like_success,
@ -393,7 +392,7 @@ fun ForumPage(
) )
) )
} }
onEvent<ForumUiEvent.Like.Failure>(viewModel) { viewModel.onEvent<ForumUiEvent.Like.Failure> {
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = context.getString( message = context.getString(
R.string.toast_like_failed, R.string.toast_like_failed,
@ -401,14 +400,14 @@ fun ForumPage(
) )
) )
} }
onEvent<ForumUiEvent.Unlike.Success>(viewModel) { viewModel.onEvent<ForumUiEvent.Unlike.Success> {
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = context.getString( message = context.getString(
R.string.toast_unlike_success R.string.toast_unlike_success
) )
) )
} }
onEvent<ForumUiEvent.Unlike.Failure>(viewModel) { viewModel.onEvent<ForumUiEvent.Unlike.Failure> {
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = context.getString( message = context.getString(
R.string.toast_unlike_failed, R.string.toast_unlike_failed,
@ -416,7 +415,6 @@ fun ForumPage(
) )
) )
} }
}
val isLoading by viewModel.uiState.collectPartialAsState( val isLoading by viewModel.uiState.collectPartialAsState(
prop1 = ForumUiState::isLoading, prop1 = ForumUiState::isLoading,

View File

@ -132,8 +132,7 @@ 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,
@ -153,7 +152,6 @@ fun ForumThreadListPage(
) )
} }
} }
}
val isRefreshing by viewModel.uiState.collectPartialAsState( val isRefreshing by viewModel.uiState.collectPartialAsState(
prop1 = ForumThreadListUiState::isRefreshing, prop1 = ForumThreadListUiState::isRefreshing,
initial = false initial = false

View File

@ -91,8 +91,7 @@ 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,
@ -100,10 +99,9 @@ fun HistoryListPage(
) )
) )
} }
onEvent<HistoryListUiEvent.Delete.Success>(viewModel) { viewModel.onEvent<HistoryListUiEvent.Delete.Success> {
snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success)) snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success))
} }
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()

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,8 +184,7 @@ 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)
@ -194,7 +192,6 @@ fun BlockSettingsPage(
} }
) )
} }
}
HorizontalPager( HorizontalPager(
count = 2, count = 2,
state = pagerState, state = pagerState,

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,8 +106,7 @@ 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,
@ -116,10 +114,9 @@ fun ThreadStorePage(
) )
) )
} }
onEvent<ThreadStoreUiEvent.Delete.Success>(viewModel) { viewModel.onEvent<ThreadStoreUiEvent.Delete.Success> {
scaffoldState.snackbarHostState.showSnackbar(context.getString(R.string.delete_store_success)) scaffoldState.snackbarHostState.showSnackbar(context.getString(R.string.delete_store_success))
} }
}
MyScaffold( MyScaffold(
backgroundColor = Color.Transparent, backgroundColor = Color.Transparent,
scaffoldState = scaffoldState, scaffoldState = scaffoldState,