androidmvvmandroid-jetpack-composeclean-architectureandroid-mvvm

Android Clean Architecture View Model implements callbacks from View


I was just dealing with clean architecture in Android apps and MVVM. I heard that you shouldn't pass your ViewModel directly to the screen (I use Jetpack Compose) but rather its states and every callback individually. This is intended to make testing the screen easier because the ViewModel does not have to be passed. I just found that if I use every callback like e.g. what should happen when a button is clicked as a lambda has to be passed to the screen composable, that in the long run there will be a lot of lambdas that I have to pass to the screen composable.

Now I had the following idea and wanted to ask if it was a good idea.

In my Kotlin file in which I define the Screen Composable, I create an interface “ScreenCallbacks” that has to be passed to the Composable. I implement this in my ViewModel so that I can simply pass the ViewModel as an implementation of the callback interface in my MainActivity.

Example:

HomeScreen.kt

@Composable
fun HomeScreen(states: HomeScreenViewModel.States, callbacks: HomeScreenCallbacks) {
    
}

interface HomeScreenCallbacks{
    fun onButtonClicked()
}

HomeScreenViewModel.kt

class HomeScreenViewModel : ViewModel() , HomeScreenCallbacks {

    val states = States()
    override fun onButtonClicked(){
        //Do some stuff
    }

    class States(){
        //My states
    }
}

MainActivity.kt

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CleanArchitectureTheme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    val viewModel = viewModel<HomeScreenViewModel>()
                    HomeScreen(
                        states = viewModel.states,
                        callbacks = viewModel
                    )
                }
            }
        }
    }
}

Is that a good approach? If yes, what could I improve? If no, why?


Solution

  • While I'm not an avid proponent of Clean Architecture, I don't think that implementing an interface directly in your View Model is a good idea overall.

    As explained in this answer, this sort of pattern is only going to make it harder to test your VM, and it'll also make it harder to play along your preferred injection library, among other things.

    Instead, I would recommend you follow Google's recommended approach to data handling between VM <-> Composables, as found in this codelab. In short, passing in the view model is okay -- as Compose has direct support for this -- and using Flow/LiveData to transfer and keep track of your data across multiple components/classes.

    This will not only make it easier for you move your data more freely, but also enable you to leverage the vast amount of examples and documentation that the Android advocates continuously put out there for us to use!