android - 在 jetpack compose 中使用 viewmodel 的最佳实践

标签 android kotlin android-jetpack-compose android-jetpack android-viewmodel

我毫不怀疑在可组合函数中使用 View 模型。我正在添加我的 Activity 代码,我正在传递我的Intent bundle 。

  1. 所以我想问一下,这样使用 viewmodel 在 Activity 中创建全局 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()
    }
}

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

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

场景一

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

fun InputItem(viewModel: InputViewModel = viewModel())

fun PulsePressure(viewModel: InputViewModel = viewModel())

场景二

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

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

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

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

最佳答案

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

您应该决定当您的应用处于 Activity 状态时您的 ViewModel 是否需要在内存中,或者范围限定为导航图或可组合项。

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

代替这个

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

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

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

State in jetpack Compose from 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 逻辑,或者如果您正在构建一个独立的自定义可组合项作为自定义 View 的对应物,您可以考虑在包装在 中的类中捕获 UI 逻辑,请记住 而不是 ViewModel。 这种方法的示例是我们与列表、脚手架和其他默认可组合件一起使用的任何 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 的状态可能会使它更容易与其他 Composasbles 一起使用。

例如

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 一起使用。实现主要取决于您的偏好。

关于android - 在 jetpack compose 中使用 viewmodel 的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73284058/

相关文章:

kotlin - Activity中如何调用suspend函数?

android - 暗模式下奇怪的错误颜色文本撰写

android - 无法将 math.h 放入命名空间

java - 如何使用 Activity 将值保存到特定文件以及如何使用 Android 中的服务进行访问?

kotlin - 如何在 Jetpack Compose 中抛出 ScrollState?

使用带有数据的 Runtime.getRuntime.exec 错误的 kotlin 中的 CURL POST 请求

javascript - 检查 Android 浏览器控制台日志

android - 在 Activity 中禁用 SlidingMenu

java - 通过注释 Kotlin 限制参数中的值

android - jetpack compose 中的图层列表相当于什么?