[英]Jetpack Compose - Click of button doesn't work each time
我開發了一個小型噴氣背包組合項目。 單擊按鈕時我遇到了問題。
此外,我為這個項目使用了一些基類/函數設計。 項目使用BaseViewModel
class 和BaseComposableScreen
可組合 function 來概括視圖和視圖模型的基本通信。
這是基本的東西:
@Composable
fun <State, Event> BaseComposableScreen(
navController: NavController,
viewModel: BaseViewModel<State, Event>,
content: @Composable (coroutineScope: CoroutineScope) -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(Unit) {
viewModel.effect.collect { effect ->
when (effect) {
is BasicEffect.NavigateToEffect -> {
coroutineScope.launch {
navController.navigate(effect.route)
}
}
is BasicEffect.NavigateBackToEffect -> {
coroutineScope.launch {
navController.popBackStack(effect.destination, effect.inclusive)
}
}
is BasicEffect.NavigateBackEffect -> {
coroutineScope.launch {
navController.popBackStack()
}
}
}
}
}
content(coroutineScope)
}
abstract class BaseViewModel<State, Event> : ViewModel() {
private val mutex = Mutex()
private val exceptionHandler = CoroutineExceptionHandler(::onError)
abstract fun provideInitialState(): State
private val _state = MutableStateFlow(provideInitialState())
val state: StateFlow<State> = _state.asStateFlow()
private val _effect = Channel<BaseEffect>(Channel.BUFFERED)
val effect: Flow<BaseEffect> = _effect.receiveAsFlow()
//optional override
open fun onEvent(event: Event) {}
open fun onError(context: CoroutineContext, throwable: Throwable) {
}
protected fun emitState(state: State) {
launchOnMain {
mutex.withLock {
_state.emit(state)
}
}
}
protected fun emitEffect(effect: BaseEffect) {
launchOnMain {
_effect.send(effect)
}
}
protected fun <P, R, U : BaseUseCase<P, R>> executeUseCase(
useCase: U,
param: P,
onComplete: ((R) -> Unit)? = null,
) {
launchOnMain {
val result = useCase.start(param)
onComplete?.invoke(result)
}
}
protected fun launchOnMain(block: suspend CoroutineScope.() -> Unit): Job {
return viewModelScope.launch(exceptionHandler, block = block)
}
protected fun launchOnIO(block: suspend CoroutineScope.() -> Unit): Job {
return viewModelScope.launch(exceptionHandler + Dispatchers.IO, block = block)
}
protected fun launchOnDefault(block: suspend CoroutineScope.() -> Unit): Job {
return viewModelScope.launch(exceptionHandler + Dispatchers.Default, block = block)
}
protected fun <T> Flow<T>.launchFlow(scope: CoroutineScope = viewModelScope): Job =
this.catch {
exceptionHandler.handleException(currentCoroutineContext(), it)
}.launchIn(scope)
}
abstract class BaseEffect
sealed class BasicEffect: BaseEffect() {
data class NavigateToEffect(val route: String) : BaseEffect()
data class NavigateBackToEffect(
val destination: String,
val inclusive: Boolean = false,
) : BaseEffect()
object NavigateBackEffect : BaseEffect()
}
我已經為可組合屏幕及其視圖 model 實現了這些基本結構。 他們來了:
class ChurchViewModel : BaseViewModel<Unit, ChurchEvent>() {
override fun provideInitialState() = Unit
override fun onEvent(event: ChurchEvent) {
when (event) {
is ChurchEvent.PrayToGod -> {
emitEffect(ChurchEffect.GodListen)
}
}
}
}
sealed class ChurchEffect : BaseEffect() {
object GodListen : ChurchEffect()
}
sealed class ChurchEvent {
object PrayToGod : ChurchEvent()
}
@Composable
fun ChurchScreen(navController: NavController) {
val viewModel = viewModel<ChurchViewModel>()
val scaffoldState = rememberScaffoldState()
LaunchedEffect(Unit) {
viewModel.effect.collect { effect ->
when (effect) {
ChurchEffect.GodListen -> {
scaffoldState.snackbarHostState.showSnackbar("God listens..")
}
}
}
}
BaseComposableScreen(navController = navController, viewModel = viewModel) {
ChurchScreenContent(
scaffoldState = scaffoldState,
onPrayToGod = {
viewModel.onEvent(ChurchEvent.PrayToGod)
}
)
}
}
@Composable
private fun ChurchScreenContent(
scaffoldState: ScaffoldState = rememberScaffoldState(),
onPrayToGod: () -> Unit = { },
) {
Scaffold(scaffoldState = scaffoldState) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly,
) {
Text(text = "Church")
Icon(
painter = painterResource(id = R.drawable.ic_baseline_location_city_24),
contentDescription = "Church image",
modifier = Modifier.size(90.dp),
)
Button(onClick = onPrayToGod) {
Text(text = "Pray to God")
}
}
}
}
問題是當我點擊“向上帝祈禱”按鈕時。 它調用的代碼只能在奇數次運行。 例如,第一次點擊有效,第二次無效。 第三次點擊有效,第四次無效。
我不知道具體原因,請幫我澄清一下這種情況!
由LaunchedEffect
啟動的協程用於監聽效果並顯示 snackbar。 我假設這些操作可以相互阻塞,所以我建議您使用單獨的協程 scope 來顯示小吃店。
@Composable
fun ChurchScreen(navController: NavController) {
val viewModel = viewModel<ChurchViewModel>()
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope() // Here is your scope for showing snackbar
LaunchedEffect(Unit) {
viewModel.effect.collect { effect ->
when (effect) {
ChurchEffect.GodListen -> {
coroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar("God listens..")
}
}
}
}
}
...
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.