I've just started looking at Room, Coroutines, and Flow, and have come across something odd: what I'm expecting to be an empty flow actually has one null item in it.
My setup is as follows, with generic T for my actual entities.
interface TDao {
    @Query("SELECT * FROM Table WHERE date=:date")
    fun getT(date: String): Flow<T>
}
@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
                }
            }
Now, when the database doesn't have any T in it for date, I'm expecting it to return an empty flow, with no items in it. Instead, it has one null element, which should never happen, because T isn't nullable.
I wrote this test for it:
@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)
        }
    }
}
...and it passes, cause, again, there's one null item returned.
What am I missing? What's wrong here, because I can't quite figure it out. Why am I getting a single null element in a flow with non nullable elements?
Room is a db written in Java and that's why it ignores Kotlin optional. I suggest to declare always query return type or, in your case, Flow<T?> type, optional. If you don't want a null type in the flow you can use filterNotNull() function like this: 
Tdao.getT(date).filterNotNull()
                        How Room handles nullability depends on how you define the return type of the query function. The Docs say:
- When the return type is
 Flow<T>, querying an empty table throws a null pointer exception.- When the return type is
 Flow<T?>, querying an empty table emits anullvalue.- When the return type is
 Flow<List<T>>, querying an empty table emits an empty list.
The above snippet discusses empty tables, but I assume the same behaviour is applicable to any query that returns no rows.
Source: Room Docs on Query
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With