我开发了一个小型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/