Ad

How To Send Multiple Commands From A MVVM To The View Using The SingleLiveEvent Class

- 1 answer

I have a ViewModel in my android app, that has some logic, and the view needs to be adjusted/perform different things depending on the result of that logic. At first I tried to do it exclusively with observers and reacting to the state of the data in the viewmodel, but it was too complicated.

Then I found the concept of commands using the SingleLiveEvent class, and I found it good because it reminds me the same pattern when using Xamarin and the microsoft's mvvvm. One of the (few) good things that working with xamarin has ;)

Well, the problem in my case is that when I have more than one command that needs to be send to the view, the view is receiving only one command. Sometimes the last one, sometimes the first one. For example, a couple of commands that order the view to perform complicated things:

sealed class CustomCommands{
    class CustomCommand1 : CustomCommands()
    class CustomCommand2() : CustomCommands()
    class CustomCommand3() : CustomCommands()
    class CustomCommand4() : CustomCommands()
    class CustomCommand5() : CustomCommands()
}

Then in my viewModel, I have the commands SingleLiveEvent object:

 class CustomViewModel...{
val commands: SingleLiveEvent<CustomCommands> = SingleLiveEvent()

 private fun doComplicatedThingsOnTheUI() {

   GlobalScope.launch(Dispatchers.IO) {

  if (someConditionsInvolvingRestRequestsAndDatabaseOperations()){
                commands.postValue(CustomCommands.CustomCommand1())
                commands.postValue(CustomCommands.CustomCommand2())
            } else {
                commands.postValue(CustomCommands.CustomCommand3())            
                commands.postValue(CustomCommands.CustomCommand4())
            }

      commands.postValue(CustomCommands.CustomCommand5())
   }


}

}

And in the Activity/Fragment, I have the observer for the commands, that should react for each command and does the work:

class MainActivity...{

viewModel.commands.observe(this, Observer { command ->
            Rlog.d("SingleLiveEvent", "Observer received event: " + command.javaClass.simpleName)
            when (command) {
Command1->doSomething1()
Command2->doSomething2()
}

}

Well, the problem is that the view is normally receiving only the last command (Command5). But the behaviour depends on the api level of the Android SDK. By api 16, the view receives the last command. By Api 28, the view receives normally the first and the last command (for example, Command1 and Command5, but not Command2).

Maybe I'm understanding the capabilities of the SingleLiveEvent class wrong, or the whole Command thing wrong, but I need a way to allow the viewmodel to order the view to do somethings depending on the state of many objects and variables. The code above is only a sample, the reality es more complicated than that.

I don't want to use callbacks between the viewmodel and the view, because I read somewhere that that breaks the whole MVVM pattern.

Maybe someone has an advice for me. Any help would be welcome.

Thank you in advance.

Ad

Answer

I think I found a workaround, that seems to work (I have tested it only a couple of hours).

The thing is that I'm using "command.postValue(XXX)", because that piece of code is running inside a couroutine, that is, in other thread. Because of that I can not use command.value directly.

But the fact is that using command.value=Command1(), it works. I mean, the view receives all the commands sent, and very important too, with the same order as they were sent. Because of that I wrote a little funcion to send the commands to the UI switching the thread.

I'm not sure if this is correct, I'm a new to Kotlin coroutines and I have to admit that I don't understand them very well yet:

    private suspend fun sendCommandToView(vararg icommands: CustomCommands) = withContext(Dispatchers.Main) {
    icommands.forEach {
        commands.value = it
    }
}

And then I send the commands

sendCommandToView(CustomCommand1(),CustomCommand2(),CustomCommand5())

This seems to work. Thougt that the "post" method would work in a similar way, but it does not.

Regards.

Ad
source: stackoverflow.com
Ad