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

Viv*_*odi 8 android kotlin android-viewmodel android-jetpack android-jetpack-compose

我对在可组合函数中使用 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")
    }
}
Run Code Online (Sandbox Code Playgroud)

我有很多可组合的函数

输入

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

输入项

@Composable
fun InputItem() {
    Image()
    PulsePressure()
}
Run Code Online (Sandbox Code Playgroud)

脉压

@Composable
fun PulsePressure() {
    Column {
        InputWithUnitContainer()
        InputWithUnitContainer()
    }
}
Run Code Online (Sandbox Code Playgroud)

输入带单元容器

@Composable
fun InputWithUnitContainer() {
    Row() {
        Text()
        TextField(value = "")
        Text()
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

场景1

fun Input(optionData: OptionData,viewModel: InputViewModel = viewModel())
Run Code Online (Sandbox Code Playgroud)

fun InputItem(viewModel: InputViewModel = viewModel())
Run Code Online (Sandbox Code Playgroud)

fun PulsePressure(viewModel: InputViewModel = viewModel())
Run Code Online (Sandbox Code Playgroud)

场景2

fun Input(optionData: OptionData,viewModel: InputViewModel = viewModel()) {
        InputItem(viewModel)
}
Run Code Online (Sandbox Code Playgroud)

fun InputItem(viewModel: InputViewModel) {
      PulsePressure(viewModel)
}
Run Code Online (Sandbox Code Playgroud)

fun PulsePressure(viewModel: InputViewModel) {
    // more function call
}
Run Code Online (Sandbox Code Playgroud)

那么你们对jetpack compose有什么建议呢?如果您不明白我的问题,请问我。非常感谢

Thr*_*ian 9

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

您应该决定当您的应用程序处于活动状态或作用域为导航图或可组合项时,您的 ViewModel 是否需要位于内存中。

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

而不是这个

fun Input(optionData: OptionData,viewModel: InputViewModel = viewModel()) {
        InputItem(viewModel)
}
Run Code Online (Sandbox Code Playgroud)

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

fun Input(optionData: OptionData, someOtherData, onOptionDataChanged:()->Unit, onSomeOtherDataChanged: () -> Unit) {
        InputItem(viewModel)
}
Run Code Online (Sandbox Code Playgroud)

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")
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的一点是,如果 UI 逻辑不依赖于任何业务逻辑,或者如果您正在构建独立的自定义可组合项作为自定义视图的对应项,则可以考虑在包装的类(而不是rememberViewModel)中捕获 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) }
        )
    }
} 
Run Code Online (Sandbox Code Playgroud)

额外的

另外,根据您的需求或适用性,优先使用带有Modifierover 的状态Composable可能会使其易于与其他可组合项一起使用。

例如

class MyState(val color:Color)

@composable
fun rememberMyState(color:Color) = remember{MyState(color)}
Run Code Online (Sandbox Code Playgroud)

将 UI 逻辑包装在 Modifier 中

fun Modifier.myModifier(myState:State)= this.then(
   Modifier.color(myState.color)
)
Run Code Online (Sandbox Code Playgroud)

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

@Composable
fun MyComposable(myState: MyState) {
   Column(Modifier.background(color){...}
}
Run Code Online (Sandbox Code Playgroud)

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