英文:
Jetpack Compose: How good is having a separate ViewModel for every interactive component on Screen
问题
我在Jetpack Compose中有一个类似聊天界面的界面。每条消息可以是文本、音频、视频或图像。
每条音频消息都是通过AudioPlayer composable显示的,并附有一个控制器。屏幕的ViewModel具有一个AudioPlayerController的单一实例,每个组件都从该控制器的StateFlow<UiState>
中收集其状态。UiState包含诸如audioFilePath、fileName、duration、progress等状态。我根据filePath确定状态中的哪个播放器正在播放。
@Composable
fun AudioPlayerUi(audioFilePath:String, ...) {
val uiState by controller.uiState.collectAsState()
val isPlaying by remember { derivedStateOf { audioFilePath == uiState.audioFilePath } }
...
}
屏幕上还有许多具有类似逻辑的组件。
我的问题是,如果我的控制器扩展了ViewModel并在每个组件中创建一个该控制器的实例,这样做好吗?
@Composable
fun AudioPlayerUi(audioFilePath:String, ...) {
val controller by viewModel<AudioPlayerController>()
val uiState by controller.uiState.collectAsState()
val isPlaying = uiState.isPlaying
...
}
现在有更多的问题随之而来。
- 这种方法好吗?
- 内存方面的考虑,因为对于每个组件都有一个viewModel实例,那些在屏幕上不可见的组件怎么办?它们的viewModels是否仍然存在于内存中?
我的屏幕大致如下:
ReceivedText
ReceivedAudio
SentText
SentAudio
SentImage
ReceivedImage
SentText
ReceivedAudio
ReceivedImage
SentAudio
SentImage
##### 输入小部件 #####
##### 可以是任何一个 ####
TestInput,AudioRecorder,
相机,等等
#########################
每个发送的组件也具有编辑功能。
我阅读了大多数在线可用的文章和文档,但没有一个提及每个组件都有其自己的viewModel。此外,我无法将所有逻辑放在屏幕ViewModel中,因为每个控制器本身都很大。
英文:
I have chat like interface in Jetpack compose. Every message can either be Text, Audio, Video, or Image.
Each Audio message is displayed with the help of a AudioPlayer composable, and an attached controller to it. The screen ViewModel has a single instance of AudioPlayerController, and each component collects its state from that controller's StateFlow<UiState>
. UiState contains states like audioFilePath, fileName, duration, progress, etc. I'm determining which player of the state is playing based on the filePath.
@Composable
fun AudioPlayerUi(audioFilePath:String, ...) {
val uiState by controller.uiState.collectAsState()
val isPlaying by remember { derivedStateOf { audioFilePath == uiState.audioFilePath } }
...
}
And there are many more components on the screen which have similar logic.
My question is how good is it if my controller extends ViewModel and I create an instance of that controller in each component?
@Composable
fun AudioPlayerUi(audioFilePath:String, ...) {
val controller by viewModel<AudioPlayerController>()
val uiState by controller.uiState.collectAsState()
val isPlaying = uiState.isPlaying
...
}
Now there are more questions following it.
- How good is this approach
- Memory concerns, since there is a viewModel instance for each component, what about the components which are not visible on the screen. Do their viewModels still live in the memory?
My Scrren is something like:
ReceivedText
ReceivedAudio
SentText
SentAudio
SentImage
ReceivedImage
SentText
ReceivedAudio
ReceivedImage
SentAudio
SentImage
##### INPUT WIDGETS #####
##### Can be any one ####
TestInput, AudioRecorder,
Camera, etc
#########################
Each sent component has editing feature as well.
I read most articles and documentations available online, but none of them mentions about having each component having it's vieeModel. Also I can't have all the logic in screen ViewModel because each controller is big in itself.
答案1
得分: 1
你当然可以这样做,但是有什么好处呢?你关心屏幕方向变化、进程终止或将ViewModel作用域限定于父导航堆栈条目吗?
我认为更简单的做法是创建一个名为AudioPlayerUiState的类,然后只需使用
val controller = remember {
AudioPlayerUiState(initialValuesFromViewModel)
}
ViewModel只是状态,或者我经常看到的术语"StateController",因为它实际上会发出状态更新。无论如何,ViewModel都会得到特殊处理,它会在配置更改、进程终止时继续存在,并且可以很好地作用于返回堆栈条目或Activity。如果你不需要这些功能,只需为你的可组合项创建一个State/StateController类,并使用remember或remember(key)将其作用域限定于可组合项中。
英文:
You could certainly do that but what are the benefits. Are you caring about orientation changes, process death or having your ViewModel scoped to the parent Navbackstackentry?
I think is simpler just having an AudioPlayerUiState class and just use
val controller = remember {
AudioPlayerUiState(initialValuesFromViewModel)
}
ViewModel is just State or the term I see often, "StateController", because it actually emit State updates. Anyway, ViewModel has especial treatment, it survivives config changes, process deaths and is nicely scope to backstack entries, or Activity. If you don't need any of those then just create a State/StateController class to your Composables and scope it to the composable with remember or remember (key)
答案2
得分: 0
使用 ViewModel
作为屏幕 UI 状态的持有者。
对于每个组件使用 ViewModel
没有额外的好处,而且会增加额外的开销。
可能会出现以下问题:
- 组件间数据共享的问题。
- 更多的内存使用。
- 生命周期问题(正如问题中已经提到的,哪些 UI 可见,哪些隐藏,
init
可能会提前发生在某些组件中等)。
对于单独的组件,您可以使用一个简单的类作为状态持有者,或者根据需要将其移动到屏幕级别的 ViewModel 来使用业务逻辑。
如何简单选择状态持有者:
- 涉及业务逻辑的屏幕级别状态 - ViewModel
- 涉及业务逻辑的组件级别状态 - 提升到 ViewModel
- 任何与 UI 特定的状态 - 使用一个简单的类作为状态持有者或在 Composables 内部。
此外,正如开头提到的,这是一个主观的问题,因此答案也是主观的。如果您认为需要并且小心处理与之相关的所有问题,可以在组件级别使用 ViewModel。
英文:
This is an opinion-based question.
(So the answer is aligned with the opinions of Android team recommendations)
To give an answer from Android Docs,
Reference - https://developer.android.com/jetpack/compose/state-hoisting#screen-ui-state
Use ViewModel
as a screen UI state holder.
Using ViewModel
for each component does not have any additional benefits and it is an additional overhead.
The following issues can happen,
- Issues in inter-component data sharing.
- More memory usage.
- Lifecycle issues. (As already mentioned in the question, what UI is visible, what is hidden,
init
can happen way earlier for some components, etc).
For individual components, you can use a simple class as a StateHolder or move to a screen-level ViewModel if required to use business logic.
How to choose State Holder in a simple way.
- Screen level state involving business logic - ViewModel
- Component level state involving business logic - Hoist to ViewModel
- Any UI-specific state - Simple class as state holder or within Composables.
To add on, as mentioned in the starting, this is an opinionated question and hence an opinionated answer. You can use ViewModel for the Component level if you see it is required and you are careful to handle all the issues related to it.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论