如何将复杂的ViewModel拆分成可重用的部分?使用MVVM,android jetpack compose

问题描述 投票:0回答:1

我有一个带有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,
                )
            }
        )
    }
}
}
android design-patterns android-jetpack-compose viewmodel android-mvvm
1个回答
0
投票

您可以使用用例将某些逻辑移出视图模型。用例是单个函数,从存储库调用函数,并可选择执行验证或过滤(组合搜索词、排序顺序并返回列表)。

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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.