繁体   English   中英

在 jetpack compose 中使用视图 model 的最佳实践

[英]Best practise of using view model in jetpack compose

我毫不怀疑在可组合的 function 中使用 viewmodel。 我正在添加我的活动代码,我正在传递我的意图包。

  1. 所以我想问一下使用这样的视图模型在活动中创建viewmodel视图模型的最佳实践是什么?

输入活动.kt

class InputActivity : ComponentActivity() {

    private val viewModel by viewModel<InputViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setupViewModel()
        setContent {
            Theme {
                AppBarScaffold(
                    displayHomeAsUpEnabled = true,
                    titleId = R.string.personal_health
                ) {
                    viewModel.OptionData?.let {
                        Input(it)
                    }
                }
            }
        }
    }

    private fun setupViewModel() {
        viewModel.optionData = intent.getParcelableExtra("optiondata")
    }
}

我有这么多可组合的 function

输入

@Composable
fun Input(optionData: OptionData) {
    var value by rememberSaveable {
        mutableStateOf(false)
    }
    Column(
        modifier = Modifier
            .fillMaxHeight()
            .verticalScroll(rememberScrollState())
        verticalArrangement = Arrangement.SpaceBetween
    ) {
        InputItem()
        Spacer()
        OnSubmitPulse()
    }
}

输入项

@Composable
fun InputItem() {
    Image()
    PulsePressure()
}

脉压

@Composable
fun PulsePressure() {
    Column {
        InputWithUnitContainer()
        InputWithUnitContainer()
    }
}

InputWithUnitContainer

@Composable
fun InputWithUnitContainer() {
    Row() {
        Text()
        TextField(value = "")
        Text()
    }
}

每个 function 都有我想存储在视图模型中的逻辑。

  1. 那么我应该在构造函数参数中创建视图模型还是每次都传递视图模型实例?

方案 1

fun Input(optionData: OptionData,viewModel: InputViewModel = viewModel())

fun InputItem(viewModel: InputViewModel = viewModel())

fun PulsePressure(viewModel: InputViewModel = viewModel())

方案 2

fun Input(optionData: OptionData,viewModel: InputViewModel = viewModel()) {
        InputItem(viewModel)
}

fun InputItem(viewModel: InputViewModel) {
      PulsePressure(viewModel)
}

fun PulsePressure(viewModel: InputViewModel) {
    // more function call
}

那么你们会在jetpack compose中提出什么建议。 如果你不明白我的问题,请问我。 非常感谢

我不认为只有一种最佳实践,而是选择适合您需求的方法。

您应该确定您的 ViewModel 是否需要在 memory 中,而您的应用程序处于活动状态或范围为导航图或 Composable。

要考虑的第二件事是您是否会在另一个屏幕或另一个应用程序中使用相同的 Composable。 如果是这样,您可以考虑通过回调将状态作为参数和事件传递给 ViewModel,而不是将 ViewModel 传递给 Composable。

而不是这个

fun Input(optionData: OptionData,viewModel: InputViewModel = viewModel()) {
        InputItem(viewModel)
}

如果我需要使用,我倾向于使用 go,或者认为我将来会在不同部分或另一个应用程序中使用Input

fun Input(optionData: OptionData, someOtherData, onOptionDataChanged:()->Unit, onSomeOtherDataChanged: () -> Unit) {
        InputItem(viewModel)
}

来自 codelabs 的 jetpack Compose 中的 State是阅读有关此主题的好文章。

@Composable
fun WellnessScreen(
    modifier: Modifier = Modifier, 
    wellnessViewModel: WellnessViewModel = viewModel()
) {
   Column(modifier = modifier) {
       StatefulCounter()

       WellnessTasksList(
           list = wellnessViewModel.tasks,
           onCheckedTask = { task, checked ->
               wellnessViewModel.changeTaskChecked(task, checked)
           },
           onCloseTask = { task ->
               wellnessViewModel.remove(task)
           }
       )
   }
}

@Composable
fun WellnessTasksList(
   list: List<WellnessTask>,
   onCheckedTask: (WellnessTask, Boolean) -> Unit,
   onCloseTask: (WellnessTask) -> Unit,
   modifier: Modifier = Modifier
) {
   LazyColumn(
       modifier = modifier
   ) {
       items(
           items = list,
           key = { task -> task.id }
       ) { task ->
           WellnessTaskItem(
               taskName = task.label,
               checked = task.checked,
               onCheckedChange = { checked -> onCheckedTask(task, checked) },
               onClose = { onCloseTask(task) }
           )
       }
   }
}

@Composable
fun WellnessTaskItem(
   taskName: String,
   checked: Boolean,
   onCheckedChange: (Boolean) -> Unit,
   onClose: () -> Unit,
   modifier: Modifier = Modifier
) {
   Row(
       modifier = modifier, verticalAlignment = Alignment.CenterVertically
   ) {
       Text(
           modifier = Modifier
               .weight(1f)
               .padding(start = 16.dp),
           text = taskName
       )
       Checkbox(
           checked = checked,
           onCheckedChange = onCheckedChange
       )
       IconButton(onClick = onClose) {
           Icon(Icons.Filled.Close, contentDescription = "Close")
       }
   }
}

最后但并非最不重要的一点是,如果它的 UI 逻辑不依赖于任何业务逻辑,或者如果您正在构建独立的自定义 Composables 作为自定义 View 的对应物,您可以考虑在 class 中捕获 UI 逻辑,该 class 包含在remember而不是 ViewModel 中。 这种方法的示例是我们与 Lists、Scaffolds 和其他默认 Composables 一起使用的任何 rememberX 函数。

例如remmeberScrollState

@Stable
class ScrollState(initial: Int) : ScrollableState {

    /**
     * current scroll position value in pixels
     */
    var value: Int by mutableStateOf(initial, structuralEqualityPolicy())
        private set

    /**
     * maximum bound for [value], or [Int.MAX_VALUE] if still unknown
     */
    var maxValue: Int
        get() = _maxValueState.value
        internal set(newMax) {
            _maxValueState.value = newMax
            if (value > newMax) {
                value = newMax
            }
        }

    /**
     * [InteractionSource] that will be used to dispatch drag events when this
     * list is being dragged. If you want to know whether the fling (or smooth scroll) is in
     * progress, use [isScrollInProgress].
     */
    val interactionSource: InteractionSource get() = internalInteractionSource

    internal val internalInteractionSource: MutableInteractionSource = MutableInteractionSource()

    private var _maxValueState = mutableStateOf(Int.MAX_VALUE, structuralEqualityPolicy())

    /**
     * We receive scroll events in floats but represent the scroll position in ints so we have to
     * manually accumulate the fractional part of the scroll to not completely ignore it.
     */
    private var accumulator: Float = 0f

    private val scrollableState = ScrollableState {
        val absolute = (value + it + accumulator)
        val newValue = absolute.coerceIn(0f, maxValue.toFloat())
        val changed = absolute != newValue
        val consumed = newValue - value
        val consumedInt = consumed.roundToInt()
        value += consumedInt
        accumulator = consumed - consumedInt

        // Avoid floating-point rounding error
        if (changed) consumed else it
    }

    override suspend fun scroll(
        scrollPriority: MutatePriority,
        block: suspend ScrollScope.() -> Unit
    ): Unit = scrollableState.scroll(scrollPriority, block)

    override fun dispatchRawDelta(delta: Float): Float =
        scrollableState.dispatchRawDelta(delta)

    override val isScrollInProgress: Boolean
        get() = scrollableState.isScrollInProgress

    /**
     * Scroll to position in pixels with animation.
     *
     * @param value target value in pixels to smooth scroll to, value will be coerced to
     * 0..maxPosition
     * @param animationSpec animation curve for smooth scroll animation
     */
    suspend fun animateScrollTo(
        value: Int,
        animationSpec: AnimationSpec<Float> = SpringSpec()
    ) {
        this.animateScrollBy((value - this.value).toFloat(), animationSpec)
    }

    /**
     * Instantly jump to the given position in pixels.
     *
     * Cancels the currently running scroll, if any, and suspends until the cancellation is
     * complete.
     *
     * @see animateScrollTo for an animated version
     *
     * @param value number of pixels to scroll by
     * @return the amount of scroll consumed
     */
    suspend fun scrollTo(value: Int): Float = this.scrollBy((value - this.value).toFloat())

    companion object {
        /**
         * The default [Saver] implementation for [ScrollState].
         */
        val Saver: Saver<ScrollState, *> = Saver(
            save = { it.value },
            restore = { ScrollState(it) }
        )
    }
} 

额外的

此外,根据您的需要或适用性,使用Modifier而非Composable的状态可能会使其易于与其他 Composable 一起使用。

例如

class MyState(val color:Color)

@composable
fun rememberMyState(color:Color) = remember{MyState(color)}

在 Modifier 中包装 UI 逻辑

fun Modifier.myModifier(myState:State)= this.then(
   Modifier.color(myState.color)
)

在某些情况下可能比 Composable 具有更多的可重用性

@Composable
fun MyComposable(myState: MyState) {
   Column(Modifier.background(color){...}
}

如果您在上面的示例中使用Composable ,我们将布局限制为Column ,而您可以将第一个与您希望的任何Composable一起使用。 实施主要取决于您的偏好。

1.所以我想问一下,在活动中使用这样的视图模型来创建全局视图模型是最佳实践吗?

更常见的是在 onCreate function 中声明它,如下所示

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: MyViewModel by viewModels()
        exampleFunction(viewModel)
    }
}

然后将 viewModel 传递给需要它的函数,声明全局 viewModel 似乎有点......反模式,我从未见过它像这样设置但它应该可以工作

  1. 那么我应该在构造函数参数中创建视图模型还是每次都传递视图模型实例?

传递 viewModel 实例(所以场景 2)就足够了,似乎没有理由一次保留同一个 viewModel 的多个实例(特别是如果它们都包含一些init函数

我首选的方法是使用CompositionLocal

按照你的例子,

//Composition Local Kept outside activity
    val VMCompositionLocal = staticCompositionLocalOf<InputViewModel> {
        error("No Escrow Viewmodel provided")
    }
    
    class InputActivity : ComponentActivity() {
    
        private val viewModel by viewModel<InputViewModel>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setupViewModel()
            setContent {
                Theme {
                    AppBarScaffold(
                        displayHomeAsUpEnabled = true,
                        titleId = R.string.personal_health
                    ) {
            // This will provide view model instance to Input Component.
                        CompositionLocalProvider(VMCompositionLocal provides viewModel){
                            Input()
                        }
                    }
                }
            }
        }
    
        private fun setupViewModel() {
            viewModel.optionData = intent.getParcelableExtra("optiondata")
        }
    }

您可以像这样从输入可组合 function 访问视图 model。

@Composable
fun Input(){
//This binds the view model instance that is provided to this Input() from setContent
var viewModel = VMCompositionLocal.current
var optionData = viewModel.optionData
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM