android - Jetpack Compose - 每次单击按钮都不起作用

标签 android kotlin mvvm android-jetpack-compose kotlin-coroutines

我开发了一个小型jetpack compose项目。我在单击按钮时遇到了问题。

此外,我在这个项目中使用了一些基类/函数设计。项目使用 BaseViewModel 类和 BaseComposableScreen 可组合函数来概括 View 和 View 模型的基本通信。

这是基本的东西:

@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()

}

我已经为可组合屏幕及其 View 模型实现了这些基本结构。它们在这里:

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")
            }
        }
    }
}

问题是当我点击“向上帝祈祷”按钮时。它调用的代码只能工作奇数次。例如,第一次单击有效,第二次则无效。第三次点击有效,第四次则无效。

我不知 Prop 体原因,请帮我澄清一下这个情况!

最佳答案

LaunchedEffect 启动的协程用于监听效果并显示 snackbar 。我假设这些操作可以互相阻塞,所以我建议您使用单独的协程作用域来显示 snackbar 。

@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..") 
                    }
                }
            }
        }
    }

    ...
}

关于android - Jetpack Compose - 每次单击按钮都不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74543333/

相关文章:

java - android.view.InflateException : Binary XML file line #22: Error inflating class ImageView

kotlin - 如何显示支持字段中的值

generics - 如何解决 Kotlin 中违反有限界限制的问题?

c# - MVVM 中子属性的 PropertyChanged

c# - 为什么在可移植库类中我无法实例化 ValidationContext 以及如何修复它?

java - Dagger 2 : avoid exporting private dependencies

android - 使 AppBarLayout/CollapsingToolbarLayout 对 ViewPager 中的 RecyclerView 中的滚动使用react

android - 使用 Kotlin 协程对 Retrofit2 进行错误处理

c# - 为什么对单个绑定(bind)项目进行更改不会刷新 ObservableCollection 中的该项目?

android - 通知按钮返回原始 Activity 后