Room with Flow 在空时返回 null

str*_*phe 4 android kotlin android-room kotlin-coroutines kotlin-flow

我刚刚开始研究 Room、Coroutines 和 Flow,并且遇到了一些奇怪的事情:我期望的空流实际上有一个空项目。

我的设置如下,T对于我的实际实体是通用的。

interface TDao {

    @Query("SELECT * FROM Table WHERE date=:date")
    fun getT(date: String): Flow<T>
}
Run Code Online (Sandbox Code Playgroud)
@Singleton
class TRepository @Inject constructor(
    private val apiService: TApiService,
    private val Tdao: TDao
) {

    suspend fun getTFor(date: String): Flow<T> =
        Tdao
            .getT(date)
            .map {
                if (it == null) {
                    returnTFromDatabase()
                } else {
                    it
                }
            }
Run Code Online (Sandbox Code Playgroud)

现在,当数据库中没有任何Tfor 时date,我希望它返回一个空流,其中没有任何项目。相反,它有一个null永远不会发生的元素,因为T它不可为空。

我为它写了这个测试:

@RunWith(AndroidJUnit4::class)
class TDatabaseTest {

    private lateinit var db: TDatabase
    private lateinit var underTest: TDao

    @Before
    fun setUp() {
        val context = InstrumentationRegistry.getInstrumentation().context
        db = Room.inMemoryDatabaseBuilder(context, TDatabase::class.java).build()
        underTest = db.TDao()
    }

    @After
    fun tearDown() {
        db.close()
    }

    @Test
    fun givenEmptyDatabase_thenHasNoItems() {
        runBlocking {
            val list = underTest.getT("1999").take(1).toList()
            assertEquals(1, list.size)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

...它通过了,因为再次null返回了一件物品。

我错过了什么?这里有什么问题,因为我无法弄清楚。为什么我在具有不可为空元素的流中得到单个空元素?

小智 25

Room 如何处理可为空性取决于您如何定义查询函数的返回类型。文件说:

  • 当返回类型为 时Flow<T>,查询空表会引发空指针异常
  • 当返回类型为 时Flow<T?>,查询空表会发出一个null值。
  • 当返回类型为 时Flow<List<T>>,查询空表会发出空列表

上面的代码片段讨论了空表,但我假设相同的行为适用于任何不返回行的查询。

资料来源:房间文档查询

  • 文档说它会引发空指针异常,但实际上我在流程中也收到了空值。 (4认同)
  • 已报告存在差异:[Room Coroutine 和 Flow @Query 未按预期处理 null](https://issuetracker.google.com/issues/213175894)。截至撰写本文时,似乎仍处于开放状态。给出的解决方案是切换到 KSP 或声明查询以返回 `Flow&lt;T?&gt;` 并根据需要进行过滤(正如 Pierluigi 在他的回答中建议的那样)。 (2认同)

小智 9

Room 是一个用 Java 编写的数据库,这就是它忽略 Kotlin 可选的原因。我建议声明始终查询返回类型,或者在您的情况下,Flow<T?>类型为可选。如果您不想在流程中使用 null 类型,您可以使用如下filterNotNull()函数:

Tdao.getT(date).filterNotNull()
Run Code Online (Sandbox Code Playgroud)