Search code examples
androidkotlinandroid-jetpack-composeandroid-roomkotlin-flow

Kotlin Flows not working as expected when Collecting items in UI Screen using collectAsState(). Also maybe taskDao.insertTask() not working?


`The Task data is not being inserted in database, therefore not collected and rendered in UI.

#TaskViewModel.kt

var taskList: StateFlow<List<Task>> = repository.getAllTasks()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

#HomeScreen.kt

val tasks by taskViewModel.taskList.collectAsState()

I was expecting test data to be displayed in UI when the app is run.

#HomeScreen.kt

@Composable
fun HomeScreen(taskViewModel: TaskViewModel, modifier: Modifier = Modifier) {

    val tasks by taskViewModel.taskList.collectAsState()

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = modifier.fillMaxSize()
    ) {
        IconButton(
            modifier = Modifier.size(100.dp),
            onClick = {

                //todo : remove after testing
                (1..10).map { index ->
                    taskViewModel.insertTask(
                        Task(
                            id = index,
                            title = "Task $index",
                            isCompleted = index % 2 == 0,
                            startTime = System.currentTimeMillis(),
                            endTime = System.currentTimeMillis() + (index * 2) * 3600000
                        )
                    )
                }
            },

            ) {
            Icon(
                imageVector = Icons.Default.Add,
                contentDescription = null,
                modifier = Modifier.size(100.dp)
            )
        }

        //Spacer(modifier = Modifier.height(10.dp))
        Card(
            shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors(Color.LightGray)
        ) {
            Text(
                text = "Tasks:", fontSize = 20.sp
            )
            Spacer(modifier = Modifier.height(10.dp))
            LazyColumn(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(16.dp, 0.dp)
            ) {
                items(items = tasks, key = { it.id }) {
                    TaskComponent(task = it)
                    Spacer(modifier = Modifier.height(10.dp))
                }
            }
        }
    }
}

#TaskComponent.kt

@Composable
fun TaskComponent(task: Task, modifier: Modifier = Modifier) {


    Box(
        modifier = Modifier
            .fillMaxWidth()
            .background(color = Color.White, shape = RoundedCornerShape(8.dp))
            .padding(start = 10.dp)
            .size(50.dp)
    ) {
        Text(text = task.title, fontSize = 20.sp)
    }
}

#TaskViewModel.kt

@HiltViewModel
class TaskViewModel @Inject constructor(private val repository: TaskRepository) : ViewModel() {

    
    var task: Task by mutableStateOf(
        Task(0, "", false, 0, 0)
    )
        private set
    

    var taskList: StateFlow<List<Task>> = repository.getAllTasks()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

    
    fun insertTask(task: Task) {
        viewModelScope.launch {
            repository.insert(task)
        }
    }

    fun updateTask(task: Task) {
        viewModelScope.launch {
            repository.update(task)
        }
    }

    fun deleteTask(task: Task) {
        viewModelScope.launch {
            repository.delete(task)
        }
    }

    fun getTaskById(id: Int) {
        viewModelScope.launch {
            repository.getTaskById(id)
        }
    }

    fun getAllTasks() {
        viewModelScope.launch {
            repository.getAllTasks()
        }
    }

    

}

#Task.kt

@Entity(tableName = "tasks_tbl")
data class Task(
    @PrimaryKey(autoGenerate = true) val id: Int=0,
    val title: String,
    val isCompleted: Boolean,
    val startTime: Long,
    val endTime: Long
)

#TaskDao.kt

package com.example.crudapp.data

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow

@Dao
interface TaskDao {
   
    @Insert
    suspend fun insertTask(task: Task) {

    }

    @Update
    suspend fun updateTask(task: Task) {

    }

    @Delete
    suspend fun deleteTask(task: Task) {

    }

    @Query("SELECT * FROM tasks_tbl WHERE id=:id")
    fun getTaskById(id: Int): Flow<Task>

    @Query("SELECT * FROM tasks_tbl")
    fun getAllTasks(): Flow<List<Task>>


}

#TaskDatabase.kt

@Database(entities = [Task::class], version = 1, exportSchema = false)
abstract class TaskDatabase : RoomDatabase() {
    abstract fun taskDao(): TaskDao
}

#TaskRepository.kt

class TaskRepository(private val dao: TaskDao) {

    
    suspend fun insert(task: Task) {
        dao.insertTask(task)
    }

    suspend fun update(task: Task) {
        dao.updateTask(task)
    }

    suspend fun delete(task: Task) {
        dao.deleteTask(task)
    }

    fun getTaskById(id: Int): Flow<Task> {
        return dao.getTaskById(id)
    }

    fun getAllTasks(): Flow<List<Task>> {
        return dao.getAllTasks()
    }
}

#AppModule.kt

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun providesLocalDatabase(@ApplicationContext context: Context): TaskDatabase {
        return Room.databaseBuilder(context, TaskDatabase::class.java, "task_db")
            .build()
    }

    @Provides
    @Singleton
    fun providesTaskDao(db: TaskDatabase): TaskDao {
        return db.taskDao()
    }

    @Provides
    @Singleton
    fun providesTaskRepository(dao: TaskDao): TaskRepository {
        return TaskRepository(dao)
    }

}

Solution

  • Remove the { } from after the function definitions in the DAO. The brackets are defining an empty (do-nothing) default implementation of the functions, but you want to allow Room to define its own implementation for these functions.

    I’m not sure if it’s intended for Room to misbehave when you have default function implementations, but maybe it is designed this way so you can define temporary test functions that behave differently.