viewModel 和 remember 函数的生命周期有什么区别?

huangapple go评论72阅读模式
英文:

What is the difference between the lifecycle of a viewModel and the remember function?

问题

我认为我可以使用ViewModel或remember函数来持久保存状态,以便我可以在函数之间共享一些状态和参数,使用以下代码。

此外,我有时会使用remember函数而不是ViewModel。

@Composable
fun ScreenHome(
recordSoundViewModel: RecordSoundViewModel = hiltViewModel(),
lineParState: LineParameterState = rememberLineParameterState()
) {

}

@Composable
fun ItemContent(
index: Int,
lineParState: LineParameterState = rememberLineParameterState(),
recordSoundViewModel: RecordSoundViewModel = hiltViewModel()
) {

}

@HiltViewModel
class RecordSoundViewModel @Inject constructor(
private val appContext: Application,
): ViewModel()
{

}

class LineParameterState private constructor() {
var isShowFirstRunPrompt by mutableStateOf(false)

val fontSizeSecondary = 12.sp

companion object {
    val instance: LineParameterState by lazy { LineParameterState() }
}

}

@Composable
fun rememberLineParameterState(): LineParameterState {
return rememberSaveable {
LineParameterState.instance
}
}

ViewModel和remember函数之间的生命周期差异是什么?

英文:

I think I can use either a ViewModel or the remember function to persist states, so I can share some states and parameters among functions using the following code.

Also, I think I use the remember function instead of ViewModel sometimes.

@Composable
fun ScreenHome(  
    recordSoundViewModel: RecordSoundViewModel = hiltViewModel(),
    lineParState: LineParameterState = rememberLineParameterState()    
) {

}

@Composable
fun ItemContent(
    index:Int,
    lineParState: LineParameterState = rememberLineParameterState(),
    recordSoundViewModel: RecordSoundViewModel = hiltViewModel()
) {

}



@HiltViewModel
class RecordSoundViewModel @Inject constructor(
    private val appContext: Application,
): ViewModel()
{

}

class LineParameterState private constructor() {
    var isShowFirstRunPrompt by mutableStateOf(false)

    val fontSizeSecondary = 12.sp

    companion object {    
        val instance: LineParameterState by lazy { LineParameterState() }
    }

}


@Composable
fun rememberLineParameterState(): LineParameterState {
     return rememberSaveable {
        LineParameterState.instance
    }
}

What are the differences in lifecycle between ViewModel and the remember function?

答案1

得分: 2

remember用于UI相关的事物,比如跟踪正在拖动的UI元素的位置或下拉菜单应该显示为选中的索引。理论上,你可以使用它来存储你的数据/模型,但这将是关注点分离不当的做法,会使UI或模型的单元测试变得非常困难。你上面的所有示例都适用于这种用法。

相反,在ViewModel中存储UI状态也是关注点分离不当的做法。

ViewModel不仅是存储数据/模型的更合理的地方,而且由于它是一个类,你可以轻松地连接到数据库和Web API,并在那里执行复杂的逻辑来填充你的状态。

ViewModel和remember都可以作用于Fragment或Activity。

英文:

remember is intended for UI-related things, like keeping track of the position of a UI element being dragged or what index of a drop-down should be shown as selected. You can theoretically use it to store your data/model, but that would be poor separation of concerns and make it very difficult to unit test either your UI or your model. All of your examples above fit this usage.

Conversely, it would also be poor separation of concerns to be storing your UI status in the ViewModel.

ViewModel is not only the more sensible place to store the data/model, but since it is a class you can easily connect to databases and web APIs and do complex logic to populate your State there.

Both ViewModel and remember can be scoped to either a Fragment or an Activity.

答案2

得分: 2

Lifecycle

ViewModel

ViewModel的生命周期与ViewModelStore的范围相关,当它在ViewModelStore上被销毁时,它会调用存储在其中的每个ViewModel的onClear方法。

getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            // 清除可用的上下文
            mContextAwareHelper.clearAvailableContext();
            // 并清除ViewModelStore
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
            mReportFullyDrawnExecutor.activityDestroyed();
        }
    }
});

但ViewModel可以在配置更改(例如旋转)中幸存下来,并且使用SavedStateHandle,您可以从SavedStateHandle或导航中获取数据。并且使用Activity范围的ViewModel,您可以在使用相同ViewModel的数据片段之间共享数据。

@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
    // 保持向后兼容性。
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // 没有人调用getViewModelStore(),因此看看是否有现有的
        // ViewModelStore来自我们上次的NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

remember

remember的生命周期与它所在的Composable相关联,当包含它的Composable退出组合时,remember会被遗忘。您可以使用RememberObserver观察数据何时被记住、遗忘或丢弃。

private class SampleUiState : RememberObserver {

    var counter by mutableStateOf(0)

    override fun onAbandoned() {
        println("🚝 onAbandoned")
    }

    override fun onForgotten() {
        println("🚆 onForgotten")
    }

    override fun onRemembered() {
        println("🚝 onRemembered")
    }

}

如果您创建一个将SampleUiState记住为以下方式的函数:

@Preview
@Composable
private fun Sample() {

    var showSample by remember {
        mutableStateOf(false)
    }
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
            .verticalScroll(rememberScrollState())
    ) {
        Button(onClick = { showSample = showSample.not() }) {
            Text(text = "Show Composable")
        }
        if (showSample) {
            RememberObserverSample()
        }
    }
}

@Composable
private fun RememberObserverSample() {
    val sampleUiState = remember {
        SampleUiState()
    }

    Button(
        modifier = Modifier.fillMaxWidth(),
        onClick = { sampleUiState.counter++ }
    ) {
        Text(text = "Increase Counter")
    }

    Text(text = "Counter: ${sampleUiState.counter}")
}

在上面的示例中,RememberObserverSample的生命周期受到它包含的remember块返回true的条件限制,您可以查看每个RememberObserver回调中返回的日志。

选择ViewModel和remember之间

ViewModel适用于用于保存来自数据层的数据的表示层,而remember通常与与UI相关的类一起使用,但这并不总是情况,因为LaunchedEffect和DisposableEffect在内部使用remember,并且这些函数也可用于调用分析、执行测量、计算、排序、调用API请求和更多非UI相关的实用程序。

DisposableEffect使用onForgotten来调用dispose函数

当构建一个UI库Composable或Modifier时记住比ViewModel更受欢迎正如您可以使用默认的rememberXState函数所示例如

@Composable
fun rememberScrollState(initial: Int = 0): ScrollState {
    return rememberSaveable(saver = ScrollState.Saver) {
        ScrollState(initial = initial)
    }
}

使用单独的状态允许您在类内部执行计算或动画,而不是在非常大的Composable中执行,还可以将其作为Modifier使用,这会比构建Composable更加灵活。

总结

如果您希望操作业务数据,可以选择ViewModel或任何表示层,如果使用MVVM/MV/MVP架构。

如果您持有屏幕状态的状态,例如加载、成功、错误,我仍然认为ViewModel是更好的选择。

当构建自定义Composable或库时,通过Modifier传递的自定义remember提供了很多灵活性,在库中创建ViewModel是多余的。

最后但并非最不重要的,remember函数不仅适用于UI数据,还适用于在没有或不希望使用外部持久状态存储(如ViewModel或其他表示层类)时为Composable函数创建内存。

@Composable
fun <T> produceState(
    initialValue: T,
    @BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
    val result = remember { mutableStateOf(initialValue) }
    LaunchedEffect(Unit) {
        ProduceStateScopeImpl(result, coroutineContext).producer()
    }
    return result
}

这在本质上是基于remember的,因为它使用了LaunchedEffect,是一个明确的remember。

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}
英文:

<h2>Lifecycle</h2>

<h3>ViewModel</h3>

ViewModel's lifecycle is scoped to ViewModelStore when it's destroyed onClear method of every ViewModel it stores are called.

    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
                mReportFullyDrawnExecutor.activityDestroyed();
            }
        }
    });

But ViewModel can survive configuration changes such as rotation and using SavedStateHandle you can get data back from SavedStateHandle or navigation. And using a Activity scoped ViewModel you can shared data between data fragments using same ViewModel.

@Override
@Nullable
@SuppressWarnings(&quot;deprecation&quot;)
public final Object onRetainNonConfigurationInstance() {
    // Maintain backward compatibility.
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null &amp;&amp; custom == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

<h3>remember</h3>

remember's lifecycle is scoped to Composable its in, when the Composable it's in exits composition remember is forgotten. You can observe when data is remembered, forgotten or abandoned with RememberObserver.

private class SampleUiState : RememberObserver {

    var counter by mutableStateOf(0)


    override fun onAbandoned() {
        println(&quot;&#128293; onAbandoned&quot;)
    }

    override fun onForgotten() {
        println(&quot;&#127944; onForgotten&quot;)
    }

    override fun onRemembered() {
        println(&quot;&#128293; onRemembered&quot;)
    }

}

If you create a function that remembers SampleUiState as

@Preview
@Composable
private fun Sample() {

    var showSample by remember {
        mutableStateOf(false)
    }
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
            .verticalScroll(rememberScrollState())
    ) {
        Button(onClick = { showSample = showSample.not() }) {
            Text(text = &quot;Show Composable&quot;)
        }
        if (showSample) {
            RememberObserverSample()
        }
    }
}

@Composable
private fun RememberObserverSample() {
    val sampleUiState = remember {
        SampleUiState()
    }

    Button(
        modifier = Modifier.fillMaxWidth(),
        onClick = { sampleUiState.counter++ }
    ) {
        Text(text = &quot;Increase Counter&quot;)
    }

    Text(text = &quot;Counter: ${sampleUiState.counter}&quot;)
}

In the sample above RememberObserverSample lifecycle so the remember it contains is limited to if block returning true as you check out logs returned in each RememberObserver callback.

<h2>Selecting between ViewModel and remember</h2>

ViewModel is suitable as presentation layer to hold data from data layer while remember is preferred with generally with Ui related class but it's not always the case since LaunchedEffect and DisposableEffect uses remember under the hood and these functions can be used to call analytics, doing for measurements, calculations, sorting, calling an api request and more non-ui related utilities as well.

DisposableEffect uses onForgotten to call dispose function.

When building an ui library or a Composable or Modifier remember is preferred over ViewModel as you can see with default rememberXState functions such as

@Composable
fun rememberScrollState(initial: Int = 0): ScrollState {
    return rememberSaveable(saver = ScrollState.Saver) {
        ScrollState(initial = initial)
    }
}

Using a separate state lets you do calculations or animations inside a class instead of a very big Composable and also making this as a Modifier gives more flexibility over building a Composable too.

<h2>Summary</h2>
If you wish to manipulate business data you can select ViewModel or any presentation layer if you are using a MVVM/MV/MVP architecture.

If you are holding state for a screen states such as loading, success, error i still think ViewModel is better option.

When you build a custom Composable or a library custom remember passed to Modifier gives a lot of flexibility and with a library it's redundant to create ViewModel.

And last but not least, remember function is not only for ui data, it's for creating memory for Composable functions when you don't have or don't want to use an external persistent state storage like ViewModel or other presentation layer classes.

You can create a timer, one time effect with scope as LaunchedEffect does, observe forgotten with DisposableEffect or create state with

@Composable
fun &lt;T&gt; produceState(
    initialValue: T,
    @BuilderInference producer: suspend ProduceStateScope&lt;T&gt;.() -&gt; Unit
): State&lt;T&gt; {
    val result = remember { mutableStateOf(initialValue) }
    LaunchedEffect(Unit) {
        ProduceStateScopeImpl(result, coroutineContext).producer()
    }
    return result
}

which in its core based on remember because of LaunchedEffect, an explicit remember.

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -&gt; Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

答案3

得分: 1

以下是代码的翻译部分:

这是一个示例

if (anything) {
    var field1 by remember { mutableStateOf(1) }
    Text(field1.toString(), modifier = Modifier.clickable { field1++ })
} else {
    var field2 by remember { mutableStateOf(1) }
    Text(field2.toString(), modifier = Modifier.clickable { field2++ })
}

如果anything发生变化,field1field2将重置为1,因为remember函数会在退出组合时被遗忘,但ViewModel不会,其生命周期与Activity一样长,甚至更长。

英文:

this is a example

if (anything) {
    var field1 by remember { mutableStateOf(1) }
    Text(field1.toString(), modifier = Modifier.clickable { field1++ })
} else {
    var field2 by remember { mutableStateOf(1) }
    Text(field2.toString(), modifier = Modifier.clickable { field2++ })
}

if anything change, field1 or field2 will reset to 1

because the remember function will be forget when leave composable

but ViewModel is not, its lifecycle as long as Activity, or even longer

huangapple
  • 本文由 发表于 2023年7月20日 08:19:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76725937.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定