Reactive Queries

Queries, Flow Return Types and Reactive Reads

A DAO function's return type decides how it runs. A suspend function runs once; a Flow function emits the result and runs again whenever a table it reads changes. Room 3.0 234 accepts nothing else, so a DAO can never block the main thread:

data/local/BookDao.kt: one-shot and reactive queries (excerpt)Kotlin
@Dao
interface BookDao {
  ...
  @Query("SELECT * FROM books ORDER BY title")
  suspend fun getAll(): List<BookEntity>                  // one-shot read
  @Query("SELECT * FROM books ORDER BY title")
  fun observeAll(): Flow<List<BookEntity>>                // re-emits after every change
  @Upsert
  suspend fun upsertAll(books: List<BookEntity>)          // insert or replace by id
  @Insert
  suspend fun addNote(note: NoteEntity): Long             // returns the new rowid
  @Transaction
  @Query("SELECT * FROM books WHERE id IN (SELECT bookId FROM notes) ORDER BY title")
  fun observeBooksWithNotes(): Flow<List<BookWithNotes>>
}

The SQL is checked while the app builds: misspell a column and KSP stops with an error naming it. Parameters bind with :name (WHERE title LIKE '%' || :query || '%'), never pasted into the SQL, and @Upsert inserts or updates by primary key, the right operation for refreshing a cache.

BookNest's reading-notes screen collects observeBooksWithNotes() in a ViewModel. Adding a note updates it without any re-query by hand:

Output of 144
D/BookNestDB: notes flow: 1 books, 1 notes
D/BookNestDB: inserted note 2 for book 3
D/BookNestDB: notes flow: 1 books, 2 notes

Room's invalidation tracker saw a write to notes and re-ran every live query that reads it. Use Flow for what a screen displays and suspend for one-off work, and keep Flow queries narrow: they re-run on any change to their tables.