簡體   English   中英

當 viewModel 中的狀態發生變化時,Jetpack compose Lazy Column 項目狀態不會改變

[英]Jetpack compose Lazy Column item state does not change when state in viewModel changes

我已經調試了應用程序,當我嘗試添加或刪除項目時,我看到 UIState 中的數據發生了變化,尤其是 isAdded 字段。 但是,即使 isAdded 發生更改,AddableItem 也不會重構。 此外,當我嘗試對項目進行排序或嘗試編寫不會發送任何 API 請求的查詢時,只需更改 TEXTFIELD 中的字符串,UI 就會重新組合。 所以 UI 會對 UIState 的變化做出反應。 我已經搜索過類似的問題,但找不到任何東西。 我相信當文件的指針發生變化時,框架必須重新組合,但事實並非如此。 知道為什么會發生這種情況或解決這個問題嗎?

這是視圖模型:

@HiltViewModel 
class AddableItemScreenViewModel@Inject constructor(
    val getAddableItemsUseCase: GetItems,
    val getItemsFromRoomUseCase: GetRoomItems,
    val updateItemCase: UpdateItem,
    savedStateHandle: SavedStateHandle) : ViewModel() {

    private val _uiState = mutableStateOf(UIState())
    val uiState: State<UIState> = _uiState

    private val _title = mutableStateOf("")
    val title: State<String> = _title

    private var getItemsJob: Job? = null

    init {
        savedStateHandle.get<String>(NavigationConstants.TITLE)?.let { title ->
            _title.value = title
        }
        savedStateHandle.get<Int>(NavigationConstants.ID)?.let { id ->
            getItems(id = id.toString())
        }
    }

    fun onEvent(event: ItemEvent) {
        when(event) {
            is ItemEvent.UpdateEvent -> {
                val modelToUpdate = UpdateModel(
                    id = event.source.id,
                    isAdded = event.source.isAdded,
                    name = event.source.name,
                    index = event.source.index
                )
                updateUseCase(modelToUpdate).launchIn(viewModelScope)
            }
            is ItemEvent.QueryChangeEvent -> {
                _uiState.value = _uiState.value.copy(
                    searchQuery = event.newQuery
                )
            }
            is ItemEvent.SortEvent -> {
                val curSortType = _uiState.value.sortType
                _uiState.value = _uiState.value.copy(
                    sortType = if(curSortType == SortType.AS_IT_IS)
                        SortType.ALPHA_NUMERIC
                    else
                        SortType.AS_IT_IS
                )
            }
        }
    }

    private fun getItems(id: String) {
        getItemsJob?.cancel()
        getItemsJob = getItemsUseCase(id)
            .combine(
                getItemsFromRoomUseCase()
            ){ itemsApiResult, roomData ->
                when (itemsApiResult) {
                    is Resource.Success -> {
                        val data = itemsApiResult.data.toMutableList()
// Look the api result, if the item is added on room, make it added, else make it not added. This ensures API call is done once and every state change happens because of room.
                        for(i in data.indices) {
                            val source = data[i]
                            val itemInRoomData = roomData.find { it.id == source.id }
                            data[i] = data[i].copy(
                                isAdded = itemInRoomData != null
                            )
                        }
                        _uiState.value = _uiState.value.copy(
                            data = data,
                            isLoading = false,
                            error = "",
                        )
                    }
                    is Resource.Error -> {
                        _uiState.value = UIState(
                            data = emptyList(),
                            isLoading = false,
                            error = itemsApiResult.message,
                        )
                    }
                    is Resource.Loading -> {
                        _uiState.value = UIState(
                            data = emptyList(),
                            isLoading = true,
                            error = "",
                        )
                    }
                }
            }.launchIn(viewModelScope)
    }
}

這是可組合的:

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun AddableItemsScreen(
    itemsViewModel: AddableItemScreenViewModel = hiltViewModel()
) {
    val state = itemsViewModel.uiState.value
    val controller = LocalNavigationManager.current
    val focusManager = LocalFocusManager.current
    val keyboardController = LocalSoftwareKeyboardController.current

    val mainScrollState = rememberLazyListState()
    val focusRequester = remember { FocusRequester() }

    // Screen UI
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colors.BackgroundColor)
            .clickable(
                indication = null,
                interactionSource = remember { MutableInteractionSource() }
            ) {
                focusManager.clearFocus()
            },
    ) {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            state = mainScrollState,
        ) {
            item {
                WhiteSpacer(
                    whiteSpacePx = 200,
                    direction = SpacerDirections.VERTICAL
                )
            }
            if (state.isLoading) {
                item {
                    ProgressIndicator()
                }
            }
            if (state.error.isNotEmpty() && state.error.isNotBlank()) {
                item {
                    ErrorText()
                }
            }
            if (state.data.isNotEmpty()) {
                val data = if (state.sortType == SortType.ALPHA_NUMERIC)
                    state.data.sortedBy { it.name }
                else
                    state.data
                data.forEach { source ->
                    if((state.searchQuery.isEmpty() && state.searchQuery.isBlank()) ||
                        (source.name != null && source.name.contains(state.searchQuery, ignoreCase =  true))) {
                        item {
                            AddableItem(
                                modifier = Modifier
                                    .padding(
                                        vertical = dimManager.heightPxToDp(20)
                                    ),
                                text = source.name ?: "",
                                isAdded = source.isAdded ?: false,
                                onItemPressed = {
                                    controller.navigate(
                                        Screens.ItemPreviewScreen.route +
                                                "?title=${source.name}" +
                                                "&id=${source.categoryId}" +
                                                "&isAdded=${source.isAdded}"
                                    )
                                },
                                onAddPressed = {
                                    itemsViewModel.onEvent(ItemEvent.UpdateEvent(source))
                                }
                            )
                        }
                    }
                }
            }
        }
        Column(
            modifier = Modifier
                .align(Alignment.TopStart)
                .background(
                    MaterialTheme.colors.BackgroundColor
                ),
        ) {
            ItemsScreenAppBar(
                title = itemsViewModel.title.value,
                onSortPressed = {
                    itemsViewModel.onEvent(ItemEvent.SortEvent)
                }
            ) {
                controller.popBackStack()
            }
            SearchBar(
                query = state.searchQuery,
                focusRequester = focusRequester,
                placeholder = itemsViewModel.title.value,
                onDeletePressed = {
                    itemsViewModel.onEvent(ItemEvent.QueryChangeEvent(""))
                },
                onValueChanged = {
                    itemsViewModel.onEvent(ItemEvent.QueryChangeEvent(it))
                },
                onSearch = {
                    keyboardController!!.hide()
                }
            )
            WhiteSpacer(
                whiteSpacePx = 4,
                direction = SpacerDirections.VERTICAL
            )
        }
    }
}

最后這是 UIState:

data class UIState(
    val data: List<ItemModel> = emptyList(),
    val isLoading: Boolean = false,
    val error: String = "",
    val searchQuery: String = "",
    val sortType: SortType = SortType.AS_IT_IS,
)

@Parcelize
data class ItemModel (
    val id: Int? = null,
    var isAdded: Boolean? = null,
    val name: String? = null,
    val index: Int? = null,

    @SerializedName("someSerializedNameForApi")
    var id: Int? = null
): Parcelable

最后,對於具有相同 UI 結構的幾乎相同的視圖模型,我遇到了類似的問題。 UI 包含一個“全部添加”按鈕,當所有內容都添加完畢后,它會變為“全部刪除”。 我還在該屏幕的 UIState 中保存按鈕的狀態。 當我嘗試添加所有項目或刪除所有項目時,UI 會重新組合。 但是當我嘗試添加或刪除單個項目時,重組不會像上面發布的代碼那樣發生。 此外,當我在該屏幕上添加所有內容時刪除一項時,按鈕的狀態確實會發生變化,但當我嘗試添加更多內容時會停止反應。 如果你們願意,我也可以分享該代碼。 我仍然不明白為什么當我嘗試在兩個屏幕上進行排序或嘗試添加-刪除所有內容時 UI 會重新組合,但在數據更改時不會重新組合,即使我更改了列表的指針地址也是如此。

謝謝你的幫助。

我無法相信答案會如此簡單,但這里有解決方案:

對於發布的屏幕,我只是將 _uiState.value = _uiState.value.copy(...) 更改為 _uiState.value = UIState(...copy and replace all with old value...) 為

_uiState.value = UIState(
                            data = data,
                            isLoading = false,
                            error = "",
                            searchQuery = _uiState.value.searchQuery,
                            sortType = _uiState.value.sortType
                        )

對於第二個屏幕,我只是通過直接發送數據而不復制來雙重更改 isAdded 值。 隨着 api 調用再次更改 isAdded 值,並且從房間流中讀取再次更改它,狀態更改了兩次。

但是,我還是想知道為什么我在 UIState 中更改數據的內存位置時,compose 沒有重新組合。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM