我已经用Compose课程完成了大部分Android基础知识,只需要做工作管理器和视图,但我已经完成了房间部分,并试图将其应用到我的第一个应用程序中,这是一个愚蠢的随机鼓舞士气的生成器.

每次打开应用程序时,我都会让一切都能生成一个新的对话,但我无法让我的按钮工作来允许用户生成新的对话.我也相信这其中有很多是可以优化的,并希望得到反馈,但目前我的重点是基本功能.

这是刀的一部分

@Query("SELECT saying from phrases WHERE type = 'greeting' ORDER BY RANDOM() LIMIT 1")
fun getGreeting(): Flow<String?>

@Query("SELECT saying from phrases WHERE type = 'first' ORDER BY RANDOM() LIMIT 1")
fun getFirst(): Flow<String?>

@Query("SELECT saying from phrases WHERE type = 'second' ORDER BY RANDOM() LIMIT 1")
fun getSecond(): Flow<String?>

@Query("SELECT saying from phrases WHERE type = 'ending' ORDER BY RANDOM() LIMIT 1")
fun getEnding(): Flow<String?>`

然后在存储库中(我相信这是可以改进的)

fun getNewTalk(): Flow<String> {
    val greeting: Flow<String?> = phraseDao.getGreeting()

    val first: Flow<String?> = phraseDao.getFirst()

    val second: Flow<String?> = phraseDao.getSecond()

    val ending: Flow<String?> = phraseDao.getEnding()

    val firstHalf = greeting.combine(first) { greeting, first ->
        "$greeting $first"
    }

    val secondHalf = second.combine(ending) { second, ending ->
        "$second $ending"
    }

    val currentTalk = firstHalf.combine(secondHalf) {firstHalf, secondHalf->
        "$firstHalf $secondHalf"
    }

    return currentTalk
}

和视图模型

class PepTalkScreenViewModel (
        private val pepTalkRepository: PepTalkRepository
    ) : ViewModel() {

    val currentTalk = pepTalkRepository.getNewTalk()

    companion object {
        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val application = (this[APPLICATION_KEY] as PepTalkApplication)
                val pepTalkRepository = application.pepTalkRepository
                PepTalkScreenViewModel(pepTalkRepository = pepTalkRepository)
            }
        }
    }
}

和主屏幕

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PepTalkScreen(
    modifier: Modifier = Modifier
){
    val pepTalkViewModel: PepTalkScreenViewModel = viewModel(factory = PepTalkScreenViewModel.Factory)
    val pepTalk by pepTalkViewModel.currentTalk.collectAsState(initial = "")
    //I've also tried collectAsStateWithLifecycle(initialValue = "") but no change in behavior

    Scaffold (
        .....
        PepTalkCard(pepTalk = pepTalk)
        .....
       bottomBar = { BottomAppBar(pepTalk = pepTalk)}

脚手架中是对可组合卡片的函数的调用

@Composable
fun PepTalkCard(
    pepTalk: String,
    modifier: Modifier = Modifier
){
    Card(
        modifier = modifier
            .fillMaxSize()
    ){
        Text(
            text = pepTalk,
            style = MaterialTheme.typography.displayMedium,
            modifier = Modifier
        )
    }
}

脚手架还调用底部的应用程序栏,其中有一个用于生成新对话的按钮和一个用于分享的按钮,这就是我的问题所在.为了让它再次查询DAO,我try 了onClick部分中的一些令人头疼的东西,但找不到我遗漏了什么.

@Composable
fun BottomAppBar (
    pepTalk: String,
    modifier: Modifier = Modifier
){
    val pepTalkViewModel: PepTalkScreenViewModel = viewModel(factory = PepTalkScreenViewModel.Factory)
    Row(
        modifier = Modifier
            .fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceEvenly
    ) {
        //region New Button
        Button(onClick = {
            pepTalkViewModel.currentTalk
        }) {

我认为调用变量curentTalk会刷新单词,因为它等于存储库中的函数.我试过做可变状态的东西,和实时数据,总是有一些东西抱怨没有匹配正确的类型,比如StateFlow来流动,感觉这应该不是一件复杂的事情.我已经在谷歌上搜索了几天了,有很多方法可以做到这一点,而且最佳实践似乎已经随着时间的推移而发生了变化,特别是在Compose方面.

任何向好的方向提供的帮助或指导都将不胜感激.

推荐答案

据我所知,让DAO流重新查询的唯一方法是修改数据库.我不确定这样做是否足够.它可能被优化为仅重新查询它可以推断可能受特定数据库更改影响的那些.

因此,如果您需要按需重新查询,我认为您需要将这些更改为挂起函数,并创建导致流重新查询的repo函数.大概是这样的:

@Query("SELECT saying from phrases WHERE type = 'greeting' ORDER BY RANDOM() LIMIT 1")
suspend fun getGreeting(): String?

@Query("SELECT saying from phrases WHERE type = 'first' ORDER BY RANDOM() LIMIT 1")
suspend fun getFirst(): String?

@Query("SELECT saying from phrases WHERE type = 'second' ORDER BY RANDOM() LIMIT 1")
suspend fun getSecond(): String?

@Query("SELECT saying from phrases WHERE type = 'ending' ORDER BY RANDOM() LIMIT 1")
suspend fun getEnding(): String?
// In repository:
private val requeryTrigger = MutableSharedFlow<Unit>(
        replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
    )

fun requestNewTalkUpdate() {
    requeryTrigger.tryEmit(Unit)
}

val newTalk: Flow<String> = requeryTrigger.map {
    val greeting = phraseDao.getGreeting()
    val first = phraseDao.getFirst()
    val second = phraseDao.getSecond()
    val ending = phraseDao.getEnding()
    "$greeting $first $second $ending"
}

然而,在这一点上,设计有点愚蠢.我们必须调用一个函数来告诉我们的流发出一个新项.相反,我们应该只有一个暂停功能来请求项目:

suspend fun generateNewTalk(): String {
    val greeting = phraseDao.getGreeting()
    val first = phraseDao.getFirst()
    val second = phraseDao.getSecond()
    val ending = phraseDao.getEnding()
    return "$greeting $first $second $ending"
}

然后,在ViewModel中,您可以创建一个State(如果愿意,也可以创建StateFlow)来保存检索到的值,并公开一个刷新函数,您可以通过按钮调用该函数:

private val _talkState = mutableStateOf("").also {
    refreshTalkState()
}
val talkState: String by _talkState

fun refreshTalkState() {
    viewModelScope.launch {
        _talkState.value = repo.generateNewTalk()
    }
}

Kotlin相关问答推荐

用浮点数或十进制数给出错误答案的阶乘计算

如何让Gradle8+在编译Kotlin代码之前编译Groovy代码?然后把它们混合在一个项目中?

Kotlin-elvis算子don';不使用map.get()

为什么使用 return instance ?: synchronized(this) { instance ?: PreferenceParameterState(context) } 时无法获得单例?

如何从 Period.between() 返回的字符串中提取信息? (Kotlin )

使用事务时未调用 Kafka ConsumerInterceptor onCommit

如何从 var list 或可变列表中获取列表流

如何将字符串格式化为电话号码kotlin算法

如何连接两个 kotlin 流?

Android 导航组件 - 向上导航打开相同的片段

在 Scaffold Jetpack Compose 内的特定屏幕上隐藏顶部和底部导航器

在 Kotlin 中实现 (/inherit/~extend) 注解

在Kotlin中不带类直接引用枚举实例

如何将命令行参数传递给Gradle Kotlin DSL

如何在Kotlin中创建填充空值的通用数组?

使用Dagger 2提供函数依赖性

Kotlin数据类打包

Java的Kotlin:字段是否可以为空?

Kotlin中对象和数据类的区别是什么?

是否可以在不使用class的情况下将 Mockito 与 Kotlin 一起使用?