我们可以将 viewmodel 作为参数传递给另一个 compose 函数吗?

Can we pass viewmodel as parameter to another compose function?

提问人:King 提问时间:11/12/2023 更新时间:11/12/2023 访问量:49

问:

我有点困惑。我们可以将 viewmodel 传递给另一个可组合函数吗?如果不是,那么将任何视图模型访问到另一个函数的好方法是什么?我在这里提供代码片段,以便您更好地理解。单击对话框中的“保存”按钮后,我正在传递用于访问房间数据库插入功能之一的视图模型。

这是我的主要功能

@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)

                }
            }

        }
    }


}

这是我的对话框。

@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")
                    }
                }
            }

        }

    }

}

我已经搜索了有关它的文章,但没有得到足够多的文章可以更好地讲述。

android kotlin android-jetpack-compose android-viewmodel

评论


答:

0赞 Edric 11/12/2023 #1

通过将整个对象传递到可重用的可组合项中,您将可重用的可组合项与对象紧密耦合,从而阻止您在其他地方轻松使用它,或者例如在不需要使用存根的情况下对其进行测试。ViewModelViewModelViewModel

“Compose 和其他库”文档的 ViewModel 部分也指出了这一点:

注意:由于 ViewModel 实例的生命周期和范围,您应该在屏幕级可组合项(即接近从 Activity 、Fragment 或 Navigation 图的目标调用的根可组合项)访问和调用 ViewModel 实例。切勿将 ViewModel 实例传递给其他可组合项,而应仅传递它们所需的数据和执行所需逻辑的函数作为参数。

因此,只需将 ViewModel 中需要的内容传递给可组合项(最好还应该用于方法名称,作为次要的吹毛求疵)。据我所知,你只使用你的方法 - 这可以简化为一个 lambda,然后你可以在你使用这个可组合物的地方调用它。dialogBoxPascalCaseinsertTodoViewModelonSaveClickinsertTodoDialogBox

也不需要直接传递状态来处理条件渲染,因为你可以在父可组合项中执行此操作:

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

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

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

无论如何,更新后的代码如下所示:

// "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
    )
  }
}
0赞 ItzDavi 11/12/2023 #2

简而言之,来自 ViewModels 最佳实践

不要将 ViewModel 传递给其他类、函数或其他 UI 组件。因为平台管理它们,所以你应该让它们尽可能靠近它。接近您的 Activity、片段或屏幕级可组合函数。这样可以防止较低级别的组件访问超出其需要的数据和逻辑。