fix: 部分页面重复 key 导致闪退

This commit is contained in:
HuanCheng65 2023-10-08 22:06:13 +08:00
parent cad7464e2b
commit 633b17f91c
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
5 changed files with 49 additions and 23 deletions

View File

@ -7,6 +7,8 @@ import com.huanchengfly.tieba.post.api.models.protos.personalized.ThreadPersonal
import com.huanchengfly.tieba.post.arch.ImmutableHolder import com.huanchengfly.tieba.post.arch.ImmutableHolder
import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock
import com.huanchengfly.tieba.post.utils.appPreferences import com.huanchengfly.tieba.post.utils.appPreferences
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@Immutable @Immutable
data class ThreadItemData( data class ThreadItemData(
@ -15,3 +17,11 @@ data class ThreadItemData(
val personalized: ImmutableHolder<ThreadPersonalized>? = null, val personalized: ImmutableHolder<ThreadPersonalized>? = null,
val hidden: Boolean = blocked && App.INSTANCE.appPreferences.hideBlockedContent, val hidden: Boolean = blocked && App.INSTANCE.appPreferences.hideBlockedContent,
) )
fun List<ThreadItemData>.distinctById(): ImmutableList<ThreadItemData> {
return distinctBy {
it.thread.get {
id
}
}.toImmutableList()
}

View File

@ -20,6 +20,7 @@ import com.huanchengfly.tieba.post.arch.UiState
import com.huanchengfly.tieba.post.arch.wrapImmutable import com.huanchengfly.tieba.post.arch.wrapImmutable
import com.huanchengfly.tieba.post.repository.FrsPageRepository import com.huanchengfly.tieba.post.repository.FrsPageRepository
import com.huanchengfly.tieba.post.ui.models.ThreadItemData import com.huanchengfly.tieba.post.ui.models.ThreadItemData
import com.huanchengfly.tieba.post.ui.models.distinctById
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -248,7 +249,7 @@ sealed interface ForumThreadListPartialChange : PartialChange<ForumThreadListUiS
is Success -> oldState.copy( is Success -> oldState.copy(
isRefreshing = false, isRefreshing = false,
forumRuleTitle = forumRuleTitle, forumRuleTitle = forumRuleTitle,
threadList = threadList.toImmutableList(), threadList = threadList.distinctById(),
threadListIds = threadListIds.toImmutableList(), threadListIds = threadListIds.toImmutableList(),
goodClassifies = goodClassifies.toImmutableList(), goodClassifies = goodClassifies.toImmutableList(),
goodClassifyId = goodClassifyId, goodClassifyId = goodClassifyId,
@ -281,7 +282,7 @@ sealed interface ForumThreadListPartialChange : PartialChange<ForumThreadListUiS
Start -> oldState.copy(isRefreshing = true) Start -> oldState.copy(isRefreshing = true)
is Success -> oldState.copy( is Success -> oldState.copy(
isRefreshing = false, isRefreshing = false,
threadList = threadList.toImmutableList(), threadList = threadList.distinctById(),
threadListIds = threadListIds.toImmutableList(), threadListIds = threadListIds.toImmutableList(),
goodClassifies = goodClassifies.toImmutableList(), goodClassifies = goodClassifies.toImmutableList(),
goodClassifyId = goodClassifyId, goodClassifyId = goodClassifyId,
@ -313,7 +314,7 @@ sealed interface ForumThreadListPartialChange : PartialChange<ForumThreadListUiS
Start -> oldState.copy(isLoadingMore = true) Start -> oldState.copy(isLoadingMore = true)
is Success -> oldState.copy( is Success -> oldState.copy(
isLoadingMore = false, isLoadingMore = false,
threadList = (oldState.threadList + threadList).toImmutableList(), threadList = (oldState.threadList + threadList).distinctById(),
threadListIds = threadListIds.toImmutableList(), threadListIds = threadListIds.toImmutableList(),
currentPage = currentPage, currentPage = currentPage,
hasMore = hasMore hasMore = hasMore

View File

@ -34,6 +34,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
import com.huanchengfly.tieba.post.ui.widgets.compose.MyLazyColumn import com.huanchengfly.tieba.post.ui.widgets.compose.MyLazyColumn
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
import kotlinx.collections.immutable.persistentListOf
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@ -59,7 +60,7 @@ fun ConcernPage(
) )
val data by viewModel.uiState.collectPartialAsState( val data by viewModel.uiState.collectPartialAsState(
prop1 = ConcernUiState::data, prop1 = ConcernUiState::data,
initial = emptyList() initial = persistentListOf()
) )
val pullRefreshState = rememberPullRefreshState( val pullRefreshState = rememberPullRefreshState(
refreshing = isRefreshing, refreshing = isRefreshing,
@ -89,7 +90,7 @@ fun ConcernPage(
) { ) {
itemsIndexed( itemsIndexed(
items = data, items = data,
key = { _, item -> "${item.recommendType}_${item.recommendUserList.size}_${item.threadList?.id}" }, key = { _, item -> "${item.recommendType}_${item.threadList?.id}" },
contentType = { _, item -> item.recommendType } contentType = { _, item -> item.recommendType }
) { index, item -> ) { index, item ->
Container { Container {

View File

@ -17,6 +17,9 @@ import com.huanchengfly.tieba.post.arch.UiIntent
import com.huanchengfly.tieba.post.arch.UiState import com.huanchengfly.tieba.post.arch.UiState
import com.huanchengfly.tieba.post.utils.appPreferences import com.huanchengfly.tieba.post.utils.appPreferences
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -91,23 +94,29 @@ class ConcernViewModel @Inject constructor() :
} }
sealed interface ConcernUiIntent : UiIntent { sealed interface ConcernUiIntent : UiIntent {
object Refresh : ConcernUiIntent data object Refresh : ConcernUiIntent
data class LoadMore(val pageTag: String) : ConcernUiIntent data class LoadMore(val pageTag: String) : ConcernUiIntent
data class Agree( data class Agree(
val threadId: Long, val threadId: Long,
val postId: Long, val postId: Long,
val hasAgree: Int val hasAgree: Int,
) : ConcernUiIntent ) : ConcernUiIntent
} }
internal fun List<ConcernData>.distinctById(): ImmutableList<ConcernData> {
return distinctBy {
it.threadList?.id
}.toImmutableList()
}
sealed interface ConcernPartialChange : PartialChange<ConcernUiState> { sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
sealed class Agree private constructor() : ConcernPartialChange { sealed class Agree private constructor() : ConcernPartialChange {
private fun List<ConcernData>.updateAgreeStatus( private fun List<ConcernData>.updateAgreeStatus(
threadId: Long, threadId: Long,
hasAgree: Int hasAgree: Int,
) : List<ConcernData> { ): ImmutableList<ConcernData> {
return map { return map {
val threadInfo = it.threadList val threadInfo = it.threadList
if (threadInfo == null) it if (threadInfo == null) it
@ -118,7 +127,7 @@ sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
threadInfo threadInfo
} }
) )
} }.toImmutableList()
} }
override fun reduce(oldState: ConcernUiState): ConcernUiState = override fun reduce(oldState: ConcernUiState): ConcernUiState =
@ -157,14 +166,14 @@ sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
Start -> oldState.copy(isRefreshing = true) Start -> oldState.copy(isRefreshing = true)
is Success -> oldState.copy( is Success -> oldState.copy(
isRefreshing = false, isRefreshing = false,
data = data, data = data.distinctById(),
hasMore = hasMore, hasMore = hasMore,
nextPageTag = nextPageTag, nextPageTag = nextPageTag,
) )
is Failure -> oldState.copy(isRefreshing = false) is Failure -> oldState.copy(isRefreshing = false)
} }
object Start: Refresh() data object Start : Refresh()
data class Success( data class Success(
val data: List<ConcernData>, val data: List<ConcernData>,
@ -183,14 +192,14 @@ sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
Start -> oldState.copy(isLoadingMore = true) Start -> oldState.copy(isLoadingMore = true)
is Success -> oldState.copy( is Success -> oldState.copy(
isLoadingMore = false, isLoadingMore = false,
data = oldState.data + data, data = (oldState.data + data).distinctById(),
hasMore = hasMore, hasMore = hasMore,
nextPageTag = nextPageTag, nextPageTag = nextPageTag,
) )
is Failure -> oldState.copy(isLoadingMore = false) is Failure -> oldState.copy(isLoadingMore = false)
} }
object Start: LoadMore() data object Start : LoadMore()
data class Success( data class Success(
val data: List<ConcernData>, val data: List<ConcernData>,
@ -209,7 +218,7 @@ data class ConcernUiState(
val isLoadingMore: Boolean = false, val isLoadingMore: Boolean = false,
val hasMore: Boolean = true, val hasMore: Boolean = true,
val nextPageTag: String = "", val nextPageTag: String = "",
val data: List<ConcernData> = emptyList(), val data: ImmutableList<ConcernData> = persistentListOf(),
): UiState ): UiState
sealed interface ConcernUiEvent : UiEvent sealed interface ConcernUiEvent : UiEvent

View File

@ -21,6 +21,7 @@ import com.huanchengfly.tieba.post.arch.wrapImmutable
import com.huanchengfly.tieba.post.models.DislikeBean import com.huanchengfly.tieba.post.models.DislikeBean
import com.huanchengfly.tieba.post.repository.PersonalizedRepository import com.huanchengfly.tieba.post.repository.PersonalizedRepository
import com.huanchengfly.tieba.post.ui.models.ThreadItemData import com.huanchengfly.tieba.post.ui.models.ThreadItemData
import com.huanchengfly.tieba.post.ui.models.distinctById
import com.huanchengfly.tieba.post.utils.appPreferences import com.huanchengfly.tieba.post.utils.appPreferences
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -276,12 +277,16 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
override fun reduce(oldState: PersonalizedUiState): PersonalizedUiState = override fun reduce(oldState: PersonalizedUiState): PersonalizedUiState =
when (this) { when (this) {
Start -> oldState.copy(isRefreshing = true) Start -> oldState.copy(isRefreshing = true)
is Success -> oldState.copy( is Success -> {
val oldSize = oldState.data.size
val newData = (data + oldState.data).distinctById()
oldState.copy(
isRefreshing = false, isRefreshing = false,
currentPage = 1, currentPage = 1,
data = (data + oldState.data).toImmutableList(), data = newData,
refreshPosition = if (oldState.data.isEmpty()) 0 else data.size refreshPosition = if (oldState.data.isEmpty()) 0 else (newData.size - oldSize),
) )
}
is Failure -> oldState.copy( is Failure -> oldState.copy(
isRefreshing = false, isRefreshing = false,
@ -307,7 +312,7 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
is Success -> oldState.copy( is Success -> oldState.copy(
isLoadingMore = false, isLoadingMore = false,
currentPage = currentPage, currentPage = currentPage,
data = (oldState.data + data).toImmutableList(), data = (oldState.data + data).distinctById(),
) )
is Failure -> oldState.copy( is Failure -> oldState.copy(