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.
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)
}