在喷气背包组合中使用视图模型的最佳做法



我毫不怀疑在可组合函数中使用视图模型。我正在添加我的活动代码,我正在传递我的意图捆绑包。

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

InputActivity.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")
}
}

我有很多可组合的功能

输入

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

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

  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
}

那么你们在喷气背包作曲中有什么建议。如果你不明白我的问题,请问我。非常感谢

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

您应该决定 ViewModel 在应用处于活动状态时是否需要位于内存中,或者范围限定为导航图或可组合对象。

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

取而代之的是这个

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

如果我需要使用,或者认为将来我会在不同的部分或其他应用程序中使用Input,我倾向于使用它

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

喷气背包中的状态 来自 Codelabs 的撰写是一篇关于这个主题的好文章。

@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 逻辑不依赖于任何业务逻辑,或者如果您正在构建独立的自定义可组合对象作为自定义视图的对应项,则可以考虑在包装在remember而不是 ViewModel 中的类中捕获 UI 逻辑。 这种方法的示例是我们与列表、基架和其他默认可组合对象一起使用的任何 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的状态可能会使其易于与其他 Composasble 一起使用。

例如

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

将 UI 逻辑包装在修饰符中

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

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

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

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

我的首选方法是使用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")
}
}

您可以像这样从输入可组合函数访问视图模型。

@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
}
1.So 我想

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

在 onCreate 函数中声明它更为常见,如下所示:

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

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

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

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

相关内容

  • 没有找到相关文章

最新更新