Flow vs Suspend in Room DAO – When to Use Which?
When working with Room Database in Jetpack Compose, choosing between Flow<T> and suspend functions in your DAO can significantly impact your app’s performance and reactivity. This guide explains the key differences and when to use each approach.
The Core Difference
The choice between Flow and suspend in Room DAO comes down to data observation vs one-time operations:
Flow<T>: For reactive, real-time data that automatically updates your UIsuspend: For single operations that execute once and complete
When to Use Flow
Use Flow for Real-Time Updates
Flow is perfect when you need your UI to automatically reflect database changes. When the underlying data changes, Flow automatically emits new values to all collectors.
Ideal use cases:
- Displaying lists that update in real-time
- Dashboard screens showing live data
- Any UI that needs to stay in sync with the database
- Observable state management in ViewModels
Example:
@Dao
interface SubjectDao {
@Query("SELECT * FROM Subject")
fun getAllSubjects(): Flow<List<Subject>> // Observes database changes
}
Using Flow in ViewModel:
class SubjectViewModel(private val dao: SubjectDao) : ViewModel() {
// Automatically updates UI when database changes
val subjects: StateFlow<List<Subject>> = dao.getAllSubjects()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
}
Using Flow in Composable:
@Composable
fun SubjectListScreen(viewModel: SubjectViewModel) {
val subjects by viewModel.subjects.collectAsState()
LazyColumn {
items(subjects) { subject ->
SubjectItem(subject)
}
}
// UI automatically updates when data changes!
}
When to Use suspend
Use suspend for One-Time Operations
suspend functions are designed for operations that execute once and don’t need to observe changes. They run asynchronously in coroutines, preventing UI thread blocking.
Ideal use cases:
- Insert, update, delete operations
- Fetching a single record by ID
- One-time data queries
- Migration or batch operations
Example:
@Dao
interface SubjectDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSubject(subject: Subject) // Single insert
}
Using suspend in ViewModel:
class SubjectViewModel(private val dao: SubjectDao) : ViewModel() {
fun addSubject(name: String) {
viewModelScope.launch {
val subject = Subject(name = name, isActive = true)
dao.insertSubject(subject) // Executes once
}
}
}
Quick Comparison Table
| Feature | Flow | suspend |
|---|---|---|
| Purpose | Observe data changes | One-time operation |
| Emission | Multiple values over time | Single result |
| UI Updates | Automatic when data changes | Manual trigger needed |
| Best for | SELECT queries (lists) | INSERT, UPDATE, DELETE |
| Lifecycle | Continuous stream | Completes after execution |
| Thread Safety | Built-in | Coroutine context |
Common Patterns
Pattern 1: List Screen with CRUD Operations
@Dao
interface SubjectDao {
// Flow for displaying the list (auto-updates)
@Query("SELECT * FROM Subject ORDER BY name ASC")
fun getAllSubjects(): Flow<List<Subject>>
// suspend for modifying data (one-time operations)
@Insert
suspend fun insertSubject(subject: Subject)
@Update
suspend fun updateSubject(subject: Subject)
@Delete
suspend fun deleteSubject(subject: Subject)
}
Pattern 2: Detail Screen
@Dao
interface SubjectDao {
// Flow for observing single item changes
@Query("SELECT * FROM Subject WHERE subjectId = :id")
fun getSubjectByIdFlow(id: Int): Flow<Subject?>
// suspend for one-time fetch (e.g., navigation argument)
@Query("SELECT * FROM Subject WHERE subjectId = :id")
suspend fun getSubjectById(id: Int): Subject?
}
Best Practices
DO:
- Use
Flowfor queries that power your UI lists - Use
suspendfor all write operations (insert, update, delete) - Collect
FlowinviewModelScopewithstateIn()for better lifecycle management - Use
suspendwhen you need the result immediately for business logic
DON’T:
- Don’t use
Flowfor write operations - Don’t use
suspendif you need real-time updates - Don’t collect
Flowdirectly in Composables without lifecycle awareness - Don’t block the main thread – always use coroutines
Performance Considerations
Flow Advantages:
- Automatic caching and deduplication
- Lifecycle-aware by default
- Efficient for frequently changing data
- Reduces boilerplate code
suspend Advantages:
- Lower memory footprint for one-time operations
- Simpler for non-reactive scenarios
- Better control over execution timing
- Easier error handling for single operations