Search code examples
androidkotlinandroid-livedataandroid-viewmodel

switchMap not trigger when LiveData changed


I followed this documentation: Transformations | Android Developers

The idea is that when the value of currentList changes, the data of activeTasks, completedTasks and importantTasks will be automatically updated and then the observers will update the UI.

The problem I'm having is that when the value of currentList changes, the switchMaps that I have assigned to the LiveData are not triggered at all.

I tried to find the solution but i don't know exactly how to google about this problem.

here's my ViewModel:

class AppViewModel(application: Application) : AndroidViewModel(application) {

    companion object {
        const val TAG: String = "AppViewModel"
    }

    private val dataSource: TaskDao

    val lists: LiveData<List<TaskList>>
    var lastAction: Int = 1
    val currentList = MutableLiveData(1)

    var currentIndex: Int = -1
        set(value) {
            field = value
            Log.d(TAG, "currentIndex setter: index = $field")
        }

    init {
        val db = AppDatabase.getInstance(application)
        dataSource = db.taskDao()
        lists = dataSource.getAllLists()
    }

    fun importantTasks(): LiveData<List<Task>> = currentList.switchMap {
//      this line for tracking changes
//      and this how i know it not getting triggered
        Log.d(TAG, "importantTasks: updated")

        dataSource.getImportant(it)
    }

    fun activeTasks(): LiveData<List<Task>> = currentList.switchMap {
        Log.d(TAG, "activeTasks: updated")
        dataSource.getActiveTasks(it)
    }

    fun completedTasks(): LiveData<List<Task>> = currentList.switchMap {
        Log.d(TAG, "completedTasks: updated")
        dataSource.getCompletedTasks(it)
    }

    fun setCurrentList(id: Int){
        currentList.value = id
    }

    fun upsert(task: Task) = viewModelScope.launch(Dispatchers.IO) {
        task.listId = currentList.value!!
        dataSource.upsert(task)
        Log.d(TAG, "upsert: task upserted at ${task.listId}")
    }

    fun delete(task: Task) = viewModelScope.launch(Dispatchers.IO) {
        task.listId = currentList.value!!
        dataSource.delete(task)
        Log.d(TAG, "delete: task deleted at ${task.listId}")
    }

    fun upsertList(list: TaskList) = viewModelScope.launch(Dispatchers.IO) {
        dataSource.upsertList(list)
        Log.d(TAG, "upsertList: updated: id = ${list.id} , name = ${list.name}")
    }
}

here's how i change the value of currentList:

    override fun onItemClicked(id: Int) {
        viewModel.setCurrentList(id)
        dismiss()
    }

UPDATE

I tried this on a new project to test if the problem was me but it works perfectly fine.

the viewmodel:

class AppViewModel : ViewModel() {

    val listId = MutableLiveData(0)

    fun getUsers(): LiveData<List<User>> = listId.switchMap {
        Log.d("AppViewModel", "getUsers: updated")
        data(it)
    }

    fun data(id: Int): LiveData<List<User>> {
        val users = if (id == 0) List(30) { i -> User(i, "User$i", "password$i") }
        else List(25) { i -> User(i, "User$i", "password$i") }
        return MutableLiveData(users)
    }
}

how i changed the value of listId:

class MainActivity : ComponentActivity() {
    val viewModel: AppViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val text = findViewById<TextView>(R.id.textCounter)
        val button = findViewById<Button>(R.id.buttonSwitch)

        viewModel.getUsers().observeForever{
            Log.d("MainActivity", "onCreate: observer triggered")
            text.text = "${it.size}"
        }
        button.setOnClickListener {
            viewModel.listId.value = if (viewModel.listId.value == 1) 0 else 1
        }
    }
}

I'm using RoomDatabase for the first project, maybe that the cause of my problem(not sure about that) because in the second one I use a specific data set and it works ok.


Solution

  • I figured it out!

    The problem cause by define 2 different instance of viewmodels in 2 different places (MainActivity and a fragment inside ViewPager2 in this case) How i define the viewmodels:

    val viewModel: AppViewModel by viewModels()
    

    and i thought that they used the same context and when i change the value in MainActivity, the fragment will notice that but no. Even though the fragment is inside the Activity, they used different context(i think). Here how i solved the problem:

    • in MainActivity:
    val viewModel: AppViewModel by viewModels()
    
    • in the fragment:
    lateinit var viewModel: AppViewModel
    
        override fun onCreateView(): View {
            viewModel = ViewModelProvider(requireActivity()).get(AppViewModel::class.java)
        }
    

    Have a nice day!!