收集代表 Jetpack Compose 中 Ui 状态的许多流的最佳方式

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

我正在尝试在我的 Android 应用程序中迁移到 Jetpack Compose。我的屏幕包含很多标志,只有当相应的标志设置为 true 时,每个项目才应该可见(并开始接收数据)。问题是我正在使用流更新项目的数据,并且我担心太多的流可能会导致主线程冻结和滞后的用户界面。

例如我的用户界面状态如下所示(这只是示例,我的真实代码更复杂,但问题是相同的):

data class UiState(
    val showFavoriteBlock: Boolean,
    val showRecentlyVisitedBlock: Boolean,
    val showMostPopularBlock: Boolean,
    val showSubscriptionBlock: Boolean,
    val showFriendsBlock: Boolean,
    val showRelatedBlock: Boolean,
    val showLatestVisitedBlock: Boolean,
    // There is 10+ same flags
    
    // And here I have content for each block which starts loading after we receive flag
    val favorites: Flow<List<...>>,
    // 10+ flows for each block
)

现在我有根可组合函数的下一个代码

@Composable
fun ExampleScreen(uiState: UiState){
    // We don't show screen untill all flags will be received,
    // flags itself are simple Booleans. But content for them is flow

    LazyColumn {
        // reading flag's value doesn't force recomposition
        // because it's not State<Boolean> and can't be changed
        if(uiState.showFavoriteBlock){
            item{
                // we sending flow, so updates in flow right now
                // cannot force recomposition of screen
                FavoritesBlock(uiState.favorites)
            }
        }
        // 10+ items for each block
        ...
    }
}

这是我的RecentlyVisitedBlock 可组合项:

@Composable
fun RecentlyVisitedBlock(recentlyVisitedFlow: Flow<List<RecentlyVisitedData>>) {
    // recentlyVisited is State<RecentlyVisitedData>
    val recentlyVisited by recentlyVisitedFlow.collectAsStateWithLifecycle()

    Row {
        // Here I'm reading state and this causes recomposition for all RecentlyVisitedBlock
        // So recomposition happens only when we receive new data from flow
    }
}

如您所见,我在可组合函数中收集了 10 多个流。还有……可以吗? Codelab、jetpack compose 示例和教程中的所有示例均包含大约 3-4 个流程。但实际应用程序可能有大约 20-30 个(甚至更多)个流,这些流可以频繁地发出数据。在后台使用流程是可以的,但这里我在 ui 上收集了所有这些。

所以我的问题是:

  • 可以有 10+(甚至 20-30)个流并在可组合函数中独立收集它们吗?可能会导致性能问题吗?因为主线程应该处理所有这些
  • 我可以将 UiState 内的所有流合并到一个中,然后使用
    filterIsInstance
    进行过滤并仅将其数据发送到每个可组合项。在这种情况下,我仍然有 10 次以上的
    collectAsState
    函数调用,但至少只有一个流程,而不是 10 次以上。可以在ui上修改流程吗?合并它们、映射、过滤。
  • 我还可以更改我的 UiState 并添加 State 字段。但在这种情况下,我将有 10 多个流和 10 多个相似的状态值。看起来图案很糟糕
  • 如果我将屏幕更改为
    ExampleScreen(uiState: Flow<UiState>)
    会怎样。在这种情况下,我只有 1 个流程,仅调用
    collectAsStateWithLifecycle
    ,但每次更改都会导致根的重组。我还可以使用衍生状态并跳过一些子项的重组。哪个更好:拥有多个独立收集和读取状态的流程,还是拥有 1 个流程,这会导致每次更新时重新组合根?

在第一种情况下,UiState 将更具可读性。在第二种情况下, 每个值都需要一个状态(例如:电子邮件、密码、 加载、错误等..)。问题是会有很多 在您的 ViewModel 和可组合项中声明。

总而言之,第一个选项提高了可读性,但会导致 到更多的重组次数。另一方面,第二个 选项更适合处理重组。

正如 @Mehdi.ncb 的评论中提到的,我试图找到 Google 的官方推荐和性能分析来决定实施哪一个 1)大屏幕级别 UiState(https://developer.android.com/jetpack) /compose/state-hoisting#screen-ui-state) 发出具有 50 多个字段的数据类,可重构整个屏幕或 2) 具有较少重构计数的单个可流动字段。

android android-jetpack-compose android-jetpack kotlin-flow
1个回答
0
投票

避免将

Flow
合并到您的
UiState
中。 UiState 应该只包含基本的、不可变的数据和模型,以确保数据不会更新,除非生成新的 UiState。从 UiState 收集流可能会导致应用程序状态不一致,因为它失去了单一事实来源。无论您的 UiState 需要多久更新一次,您的 UI 都必须始终从单个源访问最新的、一致的 UI 状态,该源可以是 ViewModel 中的
StateFlow
。我建议在您的项目中使用
ViewModel
,因为它可以更轻松地处理此问题,尤其是当您使用
Jetpack Compose
时。

例如,将

val favorites: Flow<List<...>>
替换为
val favorites: List<...>
并将 UiState 放入 ViewModel 中的 StateFlow 中,如下所示:

data class UiState(
    val showFavoriteBlock: Boolean = false,
    val showRecentlyVisitedBlock: Boolean = false,
    val showMostPopularBlock: Boolean = false,
    val showSubscriptionBlock: Boolean = false,
    val showFriendsBlock: Boolean = false,
    val showRelatedBlock: Boolean = false,
    val showLatestVisitedBlock: Boolean = false,

    val favorites: List<Favorites> = emptyList(),
    val news: List<News> = emptyList(),
)

class YourViewModel : ViewModel() {

    private val _uiState = MutableStateFlow<UiState>(UiState())
    val uiState: StateFlow<UiState> = _uiState
    ...
}

然后,您可以在需要的地方更新 ViewModel 中的 uiState,如下所示:

uiState.update {
    it.copy(
        showFavoriteBlock = false,
        favorites = favoriteItemsFromNetwork,
        news = newsItemsFromDataBase
    )
}

使用 it.copy 方法是因为 UiState 是不可变的,我们想要复制之前的状态并只修改我们需要更改的数据。

然后您可以在撰写屏幕中像这样收集您的 UiState:

val uiState = yourViewModel.uiState.collectAsStateWithLifecycle().value

并在您想要的地方使用更新后的状态。

© www.soinside.com 2019 - 2024. All rights reserved.