androidkotlinandroid-jetpack-composeandroid-viewmodel

Jetpack Compose: Should I pass ViewModel down the component tree or use callbacks?


I have a nested component structure in Jetpack Compose where a deeply nested component needs to interact with a ViewModel. I'm trying to determine the best approach for handling this interaction.

Here's my component structure:

class MainViewModel : ViewModel() {
    fun handleButtonClick() {
        // Do something
    }
}

// Approach 1: Passing ViewModel down
@Composable
fun ComposableA(viewModel: MainViewModel) {
    ComposableB()
    ComposableC(viewModel = viewModel)
}

@Composable
fun ComposableC(viewModel: MainViewModel) {
    ComposableD(viewModel = viewModel)
}

@Composable
fun ComposableD(viewModel: MainViewModel) {
    Button(onClick = { viewModel.handleButtonClick() }) {
        Text("Click Me")
    }
}

// Approach 2: Using Callbacks
@Composable
fun ComposableA(viewModel: MainViewModel) {
    val onButtonClick = { viewModel.handleButtonClick() }
    
    ComposableB()
    ComposableC(onButtonClick = onButtonClick)
}

@Composable
fun ComposableC(onButtonClick: () -> Unit) {
    ComposableD(onButtonClick = onButtonClick)
}

@Composable
fun ComposableD(onButtonClick: () -> Unit) {
    Button(onClick = onButtonClick) {
        Text("Click Me")
    }
}

Which approach is considered better practice?

Solution

  • Do not pass view models to composables.

    You want to minimize recompositions as much as possible. For Compose being able to skip functions it must determine if the parameters changed. There are a lot of optimizations under the hood, but you as a developer can make it easy: Only pass immutable and stable parameters. View models are neither so you should not pass them as parameters.

    Instead, do it like this:

    @Composable
    fun MainScreen() {
        val viewModel: MainViewModel = viewModel()
        CustomButton(onButtonClick = viewModel::handleButtonClick)
    }
    
    @Composable
    fun CustomButton(onButtonClick: () -> Unit) {
        Button(onClick = onButtonClick) {
            Text("Click Me")
        }
    }
    

    Where MainScreen is the top-most composable that needs the given view model.