Search code examples
androidmobileandroid-jetpack-composereactive-programmingkotlin-stateflow

Collecting a flow does not produce any update


In a sample project I have a viewModel with two use cases. Both use cases call the same repository. One fetches via a flow a list of tasks that stores the repository in a StateFlow. The other provides a method to add more tasks to that StateFlow.

The problem is that when I try to add a new task the flow does not emit new changes.

This are my classes:

class TaskRepositoryImpl @Inject constructor() : TaskRepository {

    private val initialTaskBusiness = mutableListOf(
        TaskBusiness(id = 1, text = "Task1"),
        TaskBusiness(id = 2, text = "Task2"),
        TaskBusiness(id = 3, text = "Task3")
    )

    private val _tasksFlow = MutableStateFlow(initialTaskBusiness.toList())
    private val tasksFlow: StateFlow<List<TaskBusiness>> = _tasksFlow

    override suspend fun getTask(): StateFlow<List<TaskBusiness>> = tasksFlow

    override suspend fun addTask(task: TaskBusiness) {
        _tasksFlow.update { _tasksFlow.value + task }
        Log.d("DEBUGME ", "Added task")
    }

    override suspend fun deleteTask(task: TaskBusiness) {
        _tasksFlow.update { _tasksFlow.value.filter { it.id != TaskDB(task).id } }
    }

}
class GetTasksUseCase @Inject constructor(private val repository: TaskRepository,) {

suspend operator fun invoke(): Flow<List<TaskBusiness>> = repository.getTask()

}

class AddTaskUseCase @Inject constructor(private val repository: TaskRepository,) {

suspend operator fun invoke(taskBusiness: TaskBusiness) {     repository.addTask(taskBusiness) }

}
@HiltViewModel
class TaskViewModel @Inject constructor(
    private val getTasksUseCase: GetTasksUseCase,
    private val addTaskUseCase: AddTaskUseCase,
) : ViewModel() {

    private val _tasks: MutableStateFlow<List<TaskViewEntry>> = MutableStateFlow(emptyList())
    val tasks get() = _tasks.asStateFlow()

    init {
        fetchTasks()
    }

    private fun fetchTasks() {
        viewModelScope.launch {
            getTasksUseCase().map { taskListBusiness ->
                taskListBusiness.map { TaskViewEntry(it) }
            }.collect { taskList ->
                Log.d("DEBUGME ", "Fetched tasks: $taskList")
                _tasks.value = taskList
            }
        }
    }

    fun onTaskAdded(text: String) {
        viewModelScope.launch {
            addTaskUseCase(
                TaskBusiness(
                    text = text
                )
            )
        }
    }

}

Even if the viewmodel is collecting that Flow it doesn't produce any emission. Even if I debug it never produces a new update in the viewModel even if the StateFlow data changes. Also if I debug tasksFlow I get 0 nCollectors. I have tried forcing the new value when adding the list to be an empty list or making sure the new list is a new value to force reactivity. I have tried to do the flows in various ways but I can't get reactivity at any time. What am I doing wrong? The next step is to create a localDataSource that replaces the in-memory storage with a room DAO but still wanted to work initially by memory.


Solution

  • The answer is the answer to this post.

    Basically all my tests with flows would have worked. The problem was that I wasn't applying a singleton from the repository (I also tried using a localdatasource at startup but I wanted to remove a level to make testing easier, with the same problem, of course).

    So when I debugged adding new tasks it was true that the StateFlow stored the new tasks correctly, but the flow I got in getTask was a different flow from a different instance of TaskRepository.

    I was using two data sources of which one was getting and the other one was updating being totally independent.

    Once I added the @Singleton tag in the hilt provider of the repository everything worked correctly.

    @InstallIn(SingletonComponent::class)
    @Module
    class DataModule() {
    
        @Singleton // This was the fix
        @Provides
        fun provideTaskRepository(localDataSource: TaskLocalDataSource): TaskRepository =
            TaskRepositoryImpl(localDataSource)
    }