简体   繁体   English

Android Paging3 - 使用 Compose 从 ViewModel 刷新

[英]Android Paging3 - refresh from a ViewModel with Compose

I'm using the Paging 3 library with Jetpack Compose and have just implemented swipe to dismiss on some paged data (using the Material library's SwipeToDismiss composable).我将Paging 3库与Jetpack Compose一起使用,并且刚刚实现了滑动以关闭某些分页数据(使用 Material 库的SwipeToDismiss可组合项)。

Once a swipe action has completed, I call a method in my ViewModel to send an update to the server (either to mark a message as read or to delete a message).完成滑动操作后,我会在我的 ViewModel 中调用一个方法来向服务器发送更新(将消息标记为已读或删除消息)。 Once this action has taken place, I obviously need to refresh the paging data.执行此操作后,我显然需要刷新分页数据。

My current approach is to have a call back from my ViewModel function which will then handle the refresh on the LazyPagingItems , but this feels wrong.我目前的方法是从我的 ViewModel function 回调,然后处理LazyPagingItems上的刷新,但这感觉不对。

Is there a better approach?有更好的方法吗?

My ViewModel basically looks like:我的 ViewModel 基本上是这样的:

@HiltViewModel
class MessageListViewModel @Inject constructor(
    private val repository: Repository
): ViewModel() {
    companion object {
        private const val TAG = "MessageListViewModel"
    }

    val messages : Flow<PagingData<Message>> = Pager(
        PagingConfig(
            enablePlaceholders = false,
        )
    ) {
        MessagePagingSource(repository)
    }.flow.cachedIn(viewModelScope)

    fun markRead(guid: String, onComplete: () -> Unit) {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                repository.markMessageRead(guid)

                onComplete()
            } catch (e: Throwable) {
                Log.e(TAG, "Error marking message read: $guid", e)
            }
        }
    }
}

And in my Composable for the message list, it looks a bit like the following:在我的消息列表的 Composable 中,它看起来有点像下面这样:

@Composable
fun MessageListScreen(
    vm: MessageListViewModel = viewModel(),
) {
    val messages: LazyPagingItems<MessageSummary> = vm.messages.collectAsLazyPagingItems()
    val refreshState = rememberSwipeRefreshState(
        isRefreshing = messages.loadState.refresh is LoadState.Loading,
    )

    Scaffold(
        topBar = {
            SmallTopAppBar (
                title = {
                    Text(stringResource(R.string.message_list_title))
                },
            )
        }
    ) { paddingValues ->
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues),
        ) {
            SwipeRefresh(
                state = refreshState,
                onRefresh = {
                    messages.refresh()
                },
            ) {
                LazyColumn(
                    modifier = Modifier.fillMaxSize(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Top,
                ) {
                    items(
                        items = messages,
                        key = { it.guid }
                    ) { message ->
                        message?.let {
                            MessageRow(
                                onMarkRead = {
                                    vm.markRead(message.guid) {
                                        messages.refresh()
                                    }
                                },
                            )
                        }
                    }
                }
            }
        }
    }
}

As I say, this does work, it just doesn't quite feel like the cleanest approach.正如我所说,这确实有效,只是感觉不太像是最干净的方法。

I'm fairly new to working with flows, so I don't know if there's some other trick I'm missing...我对使用流程还很陌生,所以我不知道是否还缺少其他一些技巧......

I ended up implementing something like this:我最终实现了这样的事情:

View Model:查看 Model:

class MessageListViewModel @Inject constructor(
    private val repository: Repository,
): ViewModel() {
    sealed class UiAction {
        class MarkReadError(val error: Throwable): UiAction()
        class MarkedRead(val id: Long): UiAction()
    }

    private val _uiActions = MutableSharedFlow<UiAction>()

    val uiActions = _uiActions.asSharedFlow()
        .shareIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(),
        )


    fun markRead(id: Long) {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                repository.markMessageRead(id)

                _uiActions.emit(UiAction.MarkedRead(id))
            } catch (e: Throwable) {
                Log.e(TAG, "Error marking message read: $id", e)

                _uiActions.emit(UiAction.MarkReadError(e))
            }
        }
    }
}

View :查看:

@Composable
fun MessageListScreen(
    vm: MessageListViewModel = viewModel(),
    onMarkReadFailed: (String) -> Unit,
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    val messages: LazyPagingItems<Message> = vm.messages.collectAsLazyPagingItems()
    val refreshState = rememberSwipeRefreshState(
        isRefreshing = messages.loadState.refresh is LoadState.Loading,
    )

    LaunchedEffect(lifecycleOwner) {
        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            vm.uiActions.collectLatest {
                when (it) {
                    is MessageListViewModel.UiAction.MarkReadError -> {
                        val msg = it.error.localizedMessage ?: it.error.message

                        val message = if (!msg.isNullOrEmpty()) {
                            context.getString(R.string.error_unknown_error_with_message, msg)
                        } else {
                            context.getString(R.string.error_unknown_error_without_message)
                        }

                        onMarkReadFailed(message)
                    }
                    is MessageListViewModel.UiAction.MarkedRead -> {
                        messages.refresh()
                    }
                }
            }
        }
    }

    SwipeRefresh(
        state = refreshState,
        onRefresh = {
            messages.refresh()
        },
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Top,
            state = listState,
        ) {
            items(
                items = messages,
                key = { it.id }
            ) { message ->
                message?.let {
                    MessageRow(
                        onMarkRead = {
                            vm.markRead(message.id)
                        },
                    )
                }

                FadedDivider()
            }

            messages.apply {
                when (loadState.append) {
                    is LoadState.Loading -> {
                        item {
                            LoadingRow(R.string.messages_loading)
                        }
                    }
                    else -> {}
                }
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM