我正在尝试在我的 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 上收集了所有这些。
所以我的问题是:
filterIsInstance
进行过滤并仅将其数据发送到每个可组合项。在这种情况下,我仍然有 10 次以上的 collectAsState
函数调用,但至少只有一个流程,而不是 10 次以上。可以在ui上修改流程吗?合并它们、映射、过滤。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) 具有较少重构计数的单个可流动字段。
避免将
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
并在您想要的地方使用更新后的状态。