[英]Jetpack Compose - Click of button doesn't work each time
I develop a small-scaled jetpack compose project.我开发了一个小型喷气背包组合项目。 I faced an issue with the click of the button.
单击按钮时我遇到了问题。
Furthermore, I've used some base class/function designs for this project.此外,我为这个项目使用了一些基类/函数设计。 Project uses
BaseViewModel
class and BaseComposableScreen
composable function to generalize basic communication of view and view-model.项目使用
BaseViewModel
class 和BaseComposableScreen
可组合 function 来概括视图和视图模型的基本通信。
Here is the base things:这是基本的东西:
@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()
}
I've implemented these base structures for a composable screen and a view model of it.我已经为可组合屏幕及其视图 model 实现了这些基本结构。 Here they are:
他们来了:
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")
}
}
}
}
Problem is that when I click the "Pray to God" button.问题是当我点击“向上帝祈祷”按钮时。 The code that it calls works only odd times.
它调用的代码只能在奇数次运行。 For example, first click works, second time is not.
例如,第一次点击有效,第二次无效。 Third click works, forth one not.
第三次点击有效,第四次无效。
I don't know the reason exactly, please, help me to clarify this situation!我不知道具体原因,请帮我澄清一下这种情况!
Your coroutine started by LaunchedEffect
is used to listen for the effects and also showing the snackbar.由
LaunchedEffect
启动的协程用于监听效果并显示 snackbar。 I assume these operations can block each other, so I recommend you to use the separate coroutine scope to show the 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.