Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Room with Flow returns null when empty

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?

like image 467
straphe Avatar asked Mar 05 '20 18:03

straphe


2 Answers

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()
like image 174
Pierluigi Avatar answered Oct 26 '22 19:10

Pierluigi


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 a null value.
  • 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

like image 43
Mauro Banze Avatar answered Oct 26 '22 18:10

Mauro Banze