Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we pass viewmodel as parameter to another compose function?

I am a bit confused. Can we pass viewmodel to another composable function? if not then what could be a good approach for accessing any viewmodel to another function? I am giving code snippets here so you can understand better. I am passing viewmodel for accessing one of room database insert function after click on "save" button in dialog box.

This is my main function

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun homeScreen(modifier: Modifier, todoViewModel: todoViewModel = viewModel()) {

    val dataList by todoViewModel.getAllTodo.collectAsState()

    val scrollBehavior = TopAppBarDefaults
        .pinnedScrollBehavior(rememberTopAppBarState())



    val openDialog = remember { mutableStateOf(true) }

    Scaffold(modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),

        topBar = {
            CenterAlignedTopAppBar(
                colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
                    containerColor = Color.Black,
                    titleContentColor = Color.White
                ),
                title = {
                    Text(
                        "TO DO LISTS",
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis
                    )
                },
                actions = {
                    IconButton(onClick = { dialogBox(todoViewModel,openDialog) }){
                        Icon(
                            imageVector = Icons.Filled.Add,
                            contentDescription = "You have to add new todo work",
                            tint = Color.White
                        )
                    }
                },
                scrollBehavior = scrollBehavior
            )
        }
    ){
        Column(modifier= modifier.padding(it)) {

            LazyColumn {
                items(dataList){list ->

                    todoItems( data = list)

                }
            }

        }
    }


}

This is my dialog box.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun dialogBox(todoViewModel: todoViewModel, openDialog: MutableState<Boolean>){

    var title by rememberSaveable{mutableStateOf("")}
    var description by rememberSaveable{mutableStateOf("")}

    Dialog(onDismissRequest = {openDialog.value = false}){

        Card(
            modifier = Modifier
                .fillMaxWidth()
                .height(200.dp)
                .padding(16.dp),
            shape = RoundedCornerShape(16.dp)
        ){

            Column(modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally) {
                OutlinedTextField(value = title,
                    onValueChange = { title = it },
                    label = { Text("Title") })
                Spacer(modifier = Modifier.padding(vertical = 12.dp))

                OutlinedTextField(value = description,
                    onValueChange = { description = it },
                    label = { Text("Description") })

                Row(modifier = Modifier
                    .fillMaxWidth(),
                    horizontalArrangement = Arrangement.Center) {

                    TextButton(onClick = {
                        todoViewModel.insertTodo(todoEntity(title = title, desciription = description))},
                        modifier = Modifier.padding(8.dp)){
                        Text("Save")
                    }
                    TextButton(onClick = {}, modifier = Modifier.padding(8.dp)){
                        Text("Dismiss")
                    }
                }
            }

        }

    }

}

I have searched the articles about it but have not gotten enough articles that can tell in a better way.

like image 276
King Avatar asked Sep 13 '25 03:09

King


1 Answers

By passing an entire ViewModel object into your reusable composables, you are tightly coupling your reusable composable to the ViewModel object, which prevents you from easily using it elsewhere, or for say testing it without requiring a stubbed ViewModel to be used.

This is also noted in the ViewModel section of the "Compose and other libraries" docs:

Note: Due to their lifecycle and scoping, you should access and call ViewModel instances at screen-level composables, that is, close to a root composable called from an activity, fragment, or destination of a Navigation graph. You should never pass down ViewModel instances to other composables, pass only the data they need and functions that perform the required logic as parameters.

As such, only pass what you need from the ViewModel down to your dialogBox composable (you should also preferably use PascalCase for method names, as a minor nitpick). From what I can tell, you're only using the insertTodo method from your ViewModel - this can be reduced down to a onSaveClick lambda, which you could then call insertTodo in where you're using this DialogBox composable.

There's also no need to pass a state directly to handle the conditional rendering when you could just do it in the parent composable:

@Composable
fun DialogBox(..., openDialog: MutableState<Boolean>) { ... }

// VS
@Composable
fun DialogBox(...) { ... }

@Composable
fun HomeScreen(...) {
  if (openDialog.value) {
    DialogBox(...)
  }
}

Anyways, here's what the updated code would look like:

// "dialogBox" is too vague, I would probably name it something more
// specific like "NewTodoDialog"
@Composable
fun NewTodoDialog(
  onDismissRequest: () -> Unit,
  // Pass back the data here...
  // You could maybe make the data be contained in a data class,
  // something like "TodoItem" or similar
  onSaveClick: (title: String, description: String) -> Unit
) {
  var title by rememberSaveable { mutableStateOf(...) }
  var description by rememberSaveable { mutableStateOf(...) }

  Dialog(onDismissRequest = onDismissRequest) {
    // Dialog content...
    // ...
    // Dialog actions:
    Row(/* ... */) {
      TextButton(
        // You can pass back the data to be added here:
        onClick = { onSaveClick(title, description) },
        modifier = Modifier.padding(8.dp)
      ) {
        Text("Save")
      }
      // I've also updated the onClick of your dismiss button to _actually_
      // do something, like say request for the dialog to be dismissed
      TextButton(
        onClick = onDismissRequest,
        modifier = Modifier.padding(8.dp)
      ) {
        Text("Dismiss")
      }
    }
  }
}

// In the parent composable:
@Composable
fun HomeScreen(viewModel: YourViewModel, /* ... */) {
  var isDialogShown by rememberSaveable { mutableStateOf(false) }
  
  // Then you can conditionally render the dialog
  if (isDialogShown) {
    NewTodoDialog(
      // This would actually dismiss the dialog
      onDismissRequest = { isDialogShown = false },
      // Method references are more ideal here if the method + lambda
      // definitions are the same.
      // Otherwise, you'll have to call it manually in a lambda
      onSaveClick = viewModel::insertTodo
    )
  }
}
like image 69
Edric Avatar answered Sep 14 '25 18:09

Edric