我有一个带有jetpack compose的MVVM模式的应用程序,代码风格与NowInAndroid应用程序结构类似。 现在我遇到了这个问题,请帮助举例,以便我可以调查并进一步采取行动。 我有复杂的屏幕,例如这是实体(它作为流程来自房间),我应该为用户提供编辑功能。该实体包含数据包括一些列表。为了编辑此列表,我在屏幕上方打开一个对话框,每个项目都是一个带有文本字段等的卡片。 另外,我需要从该应用程序的其他屏幕打开此对话框,以达到编辑相同类型列表的相同目标。
现在,每个字段的每次更改都会触发视图模型中的操作,就像来自其他屏幕组件的其他操作一样。
我所做的一切:我将对话框可组合项和每个卡片分开并使用它们。我将所有对话框操作统一在我的视图模型实现的一个界面中。 因此,现在为了重用此对话框,我应该其他视图模型实现此操作接口,但实现几乎相同!现在是 12 个动作。
现在我无法理解如何将操作的实现与视图模型分开。
同样的问题不仅涉及对话框,还涉及屏幕的任何部分(可组合),这些部分具有复杂的逻辑和操作并且应该可重用。
例如我现在拥有的代码结构。
@Composable
fun ScreenRoute(
viewModel: ExampleEntityEditViewModel = hiltViewModel(),){
//...
val exampleDialogUIStateby viewModel.exampleDialogUIState.collectAsStateWithLifecycle()
val exampleActions: ExampleDialogActions = viewModel
if (exampleDialogUIState.visible) {
ExampleDialog(
uiState = exampleDialogUIState,
actions = exampleActions,
//...
)
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
fun ExampleDialog(
uiState: ExampleDialogUIState,
actions: ExampleDialogActions,
currencies: List<Currency>,
//...
showInfoDialog: Boolean,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
messages: List<Message>,
) {
if (uiState.visible) {
ModalBottomSheetLayout(){
Scaffold(
modifier = modifier,
topBar = {
CustomTopAppBar(
titleRes = R.string.title,
navigationIcon = CustomIcons.Close,
onNavigationClick = onDismiss,
actionIcon = CustomIcons.Info,
//...
onActionClick = actions::onInfoClicked,
)
},
floatingActionButton = {
FloatingActionButton(
onClick = actions::onExampleAddClicked,
//...
)
},
) { innerPadding ->
if (uiState.items.isEmpty()) {
EmptyScreen(
modifier = Modifier.padding(innerPadding),
messageHeader = //..,
messageText = //..,
)
} else {
ExampleDialogEditContent(
modifier = Modifier.padding(innerPadding),
uiState = uiState,
actions = actions,
//...
)
}
}
if (showInfoDialog) {
MessageDialog(
//...
)
}
}
}
}
interface ExampleDialogActions {
fun onExampleAmountChanged(exampleItem: ExampleItem, value: String)
fun onExamplePeriodCountChanged(exampleItem: ExampleItem, value: String)
fun onExampleTypeSelected(exampleItem: ExampleItem, value: String)
fun onExampleSelectedClicked(id: Long)
fun onExampleDeleteClicked(exampleItem: ExampleItem)
fun onExampleAddClicked()
fun onDismissExampleDialog()
//...
fun onInfoClicked()
fun onDismissInfoDialog()
fun onExampleMessageShown(errorId: Long)
fun onCurrencySelected(currency: Currency)
}
data class ExampleDialogUIState(
val visible: Boolean,
val showInfoDialog: Boolean,
val exampleItems: List<ExampleDialogUIState>,
val currency: Currency,
) {
companion object {
val initialState = ExampleDialogUIState(
visible = false,
exampleItems = listOf(),
showInfoDialog = false,
currency = DefaultCurrency
)
}
}
@HiltViewModel
class ExampleViewModel @Inject constructor(
private val exampleRepository: ExampleRepository,
private val currencyRepository: CurrencyRepository,
private val preferencesManager: DefaultPreferencesManager,
savedStateHandle: SavedStateHandle,
) : ViewModel(), ExampleDialogActions {
//...
private val _exampleDialogUIState: MutableStateFlow<ExampleDialogUIState> =
MutableStateFlow(ExampleDialogUIState.initialState)
val exampleDialogUIState: StateFlow<ExampleDialogUIState> get() = _exampleDialogUIState
//...
override fun onShowExampleDialog() {
_exampleDialogUIState.update {
_exampleDialogUIState.value.copy(
visible = true,
exampleItems = _mainEntity.value.someList.map { item ->
ExampleItem(
item = item,
amount = item.amount.toString(),
amountInputIsError = false,
title = "",
//...
)
},
currency = _mainEntity.value.currency
)
}
}
//...
override fun onExampleAmountChanged(exampleItem: ExampleItem, value: String) {
_exampleDialogUIState.update {
val exampleItems = _exampleDialogUIState.value.exampleItems
val itemToChange = exampleItems.indexOfFirst {
it.id == exampleItem.id
}
_exampleDialogUIState.value.copy(
exampleItems = exampleItems.copy {
this[itemToChange] = this[itemToChange].copy(
amount = value,
amountInputIsError = !validateExampleAmount(value).status,
)
}
)
}
}
}
您可以使用用例将某些逻辑移出视图模型。用例是单个函数,从存储库调用函数,并可选择执行验证或过滤(组合搜索词、排序顺序并返回列表)。
class AddNote(
private val repository: NoteRepository
) {
suspend operator fun invoke(note: Note) {
if(note.title.isBlank()) {
throw InvalidNoteException("The title of the note can't be empty.")
}
if(note.content.isBlank()) {
throw InvalidNoteException("The content of the note can't be empty.")
}
repository.insertNote(note)
}
}
然后您可以将相同的用例注入到不同的视图模型中。
“此外,我需要从该应用程序的其他屏幕打开此对话框,以达到编辑相同类型列表的相同目标。”
也许有一种方法可以使用嵌套导航,其中嵌套路由将包含所选项目的 id。 (然后你就可以拥有通常的DetailsViewModel)
您可以使用
sealed class
来包含对话框所需的所有字段。
data class Item(
val title: String,
val count: Int,
)
sealed class DialogState {
data object NotShowing : DialogState()
data class Showing(
val savedItem: Item,
val draftItem: Item, // not saved to database yet
) : DialogState()
}
class Example {
val state = MutableStateFlow<DialogState>(DialogState.NotShowing)
fun changeTitle(editedItem: Item, newTitle: String) {
val dialogState = state.value
if(dialogState is DialogState.Showing){
val newDraftItem = dialogState.draftItem.copy(title = newTitle)
state.value = dialogState.copy(draftItem = newDraftItem)
}
}
}
您还可以有第二个视图模型来处理所选项目。 (
hiltViewModel
将为每条路线返回不同的实例,或者您可以使用共享视图模型)
data class State(
val selectedItem: Item? = null,
val validationErrors: Exception? = null
){
val showDialog = selectedItem != null
}
class Example {
val state = MutableStateFlow(State())
fun onItemClicked(item: Item){
state.value = state.value.copy(selectedItem = item)
}
fun onSubmit(item: Item){
// save(item)
state.value = State() // refresh
}
}