Android Room库与Kotlin Flow一起使用时,toList()方法无法正常工作。

4
我用Room和Flows制作了一个简单的示例应用程序:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val build = Room.databaseBuilder(this, FinanceDatabase::class.java, "database.db")
            .fallbackToDestructiveMigration()
            .build()
    GlobalScope.launch {
        build.currencyDao().addCurrency(CurrencyLocalEntity(1))
        val toList = build.currencyDao().getAllCurrencies().toList()
        Log.d("test", "list - $toList")
    }
}
}

@Entity(tableName = "currency")
data class CurrencyLocalEntity(
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = "currencyId")
        var id: Int
) {
    constructor() : this(-1)
}

@Dao
interface CurrencyDao {

@Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<CurrencyLocalEntity>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addCurrency(currency: CurrencyLocalEntity)
}

@Database(entities = [CurrencyLocalEntity::class], version = 1)
abstract class FinanceDatabase : RoomDatabase() {
    abstract fun currencyDao(): CurrencyDao
}

我想使用上面代码中的 toList() 函数,但出现了问题,即使日志也没有打印。同时,使用 collect() 却可以正常工作并给我所有记录。
有人能向我解释一下是什么问题吗?谢谢。

文档中的示例表明,当您想从生成多个项的查询返回Flow时,应使用Flow<List<T>>。返回Flow<T>的示例是针对返回单个项的查询的。您的getAllCurrencies()期望Room为currency表中的每个项发出信号。我认为它不支持这样做。 - Bob Snyder
5个回答

9

这里有一些问题,但我会解决主要的问题。

Room返回的Flow在每次数据库修改时都会发出查询结果。(这可能仅限于表更改而不是整个数据库)。

由于数据库可以在未来的任何时刻更改,所以Flow将 (或多或少) 永远不会完成,因为始终可能发生变化。

对返回的 Flow 调用 toList() 将永远挂起,因为 Flow 永远不会完成。从概念上讲,这是有道理的,因为 Room 不能提供每个更改的记录列表,而不等待它发生。collect 能够给你记录列表,而 toList() 不能。

你可能想要的是这样的代码:

@Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<List<CurrencyLocalEntity>>

使用 Flow<...>.first(),您可以获取查询的第一个结果。


1
如果我们在 flow 中使用 first(),那么 Table 的更改不会再次调用第一个 {} 块。但是,如果我们使用 collect {},则任何 Table 更改都会调用 collect {}。你知道有什么方法可以在第一个 {} 中继续获取 Table 更改吗? - Sharad
1
首先给你第一个变化。如果您想获取未来的更改,请使用collect。 - Dominic Fischer
我不确定内部实现细节,但你知道房间暴露的是哪种流(热流还是冷流)吗?也许是带有 while 循环的冷流?否则收集器只会在 emit 发生后监听一次。 - stdout
房间给你一个冷流。 - Dominic Fischer

2

Flow in Room用于观察表格中的变化。

当对表格进行更改时,无论更改哪一行,查询都将被重新触发并且Flow会再次发出。

然而,数据库的这种行为也意味着如果我们更新一个不相关的行,我们的Flow将会再次发出相同的结果。由于SQLite数据库触发器仅允许在表级别而不是行级别进行通知,因此Room无法知道表数据中到底发生了什么变化。


0

确保用于检索列表的相同 doa 对象也用于更新数据库。除此之外,将流转换为 LiveData 可以使用 asLivedata 扩展函数完成。


0

对于我而言,下面的解决方案可以用来更新视图与数据库表更改的值。

解决方案:插入数据到Room数据库时和从数据库获取信息时,应当使用同一个Dao对象。

如果您正在使用Dagger Hilt,则

@Singleton注释将起作用。

我希望这可以解决你的问题。


-1
**getAllCurrencies()** function should be suspend.

Please check the syntax to collect List from Flow: 

suspend fun <T> Flow<T>.toList(
    destination: MutableList<T> = ArrayList()
): List<T> (source)

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接