From 677b75f3685854b94f494dabe5c8b370f7a43982 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:27:54 +0300 Subject: [PATCH] multiplatform: possible race in ChatList (#2757) * multiplatform: possible race in ChatList * more changes --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../chat/simplex/common/model/ChatModel.kt | 9 +++++---- .../common/views/chatlist/ChatListView.kt | 17 ++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 3189e1c705..e0f1e773c3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -130,10 +130,11 @@ object ChatModel { } } - fun hasChat(id: String): Boolean = chats.firstOrNull { it.id == id } != null - fun getChat(id: String): Chat? = chats.firstOrNull { it.id == id } - fun getContactChat(contactId: Long): Chat? = chats.firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId } - private fun getChatIndex(id: String): Int = chats.indexOfFirst { it.id == id } + // toList() here is to prevent ConcurrentModificationException that is rarely happens but happens + fun hasChat(id: String): Boolean = chats.toList().firstOrNull { it.id == id } != null + fun getChat(id: String): Chat? = chats.toList().firstOrNull { it.id == id } + fun getContactChat(contactId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId } + private fun getChatIndex(id: String): Int = chats.toList().indexOfFirst { it.id == id } fun addChat(chat: Chat) = chats.add(index = 0, chat) fun updateChatInfo(cInfo: ChatInfo) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index ec5d0071c0..1e7d25346b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.* @@ -27,9 +28,8 @@ import chat.simplex.common.views.usersettings.simplexTeamUri import chat.simplex.common.platform.* import chat.simplex.common.views.newchat.* import chat.simplex.res.MR -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch import java.net.URI @Composable @@ -344,7 +344,11 @@ private fun ChatList(chatModel: ChatModel, search: String) { onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } } val showUnreadAndFavorites = remember { ChatController.appPrefs.showUnreadAndFavorites.state }.value - val chats by remember(search, showUnreadAndFavorites) { derivedStateOf { filteredChats(showUnreadAndFavorites, search) } } + val allChats = remember { chatModel.chats } + // In some not always reproducible situations this code produce IndexOutOfBoundsException on Compose's side + // which is related to [derivedStateOf]. Using safe alternative instead + // val chats by remember(search, showUnreadAndFavorites) { derivedStateOf { filteredChats(showUnreadAndFavorites, search, allChats.toList()) } } + val chats = filteredChats(showUnreadAndFavorites, search, allChats.toList()) LazyColumn( modifier = Modifier.fillMaxWidth(), listState @@ -360,13 +364,12 @@ private fun ChatList(chatModel: ChatModel, search: String) { } } -private fun filteredChats(showUnreadAndFavorites: Boolean, searchText: String): List { - val chatModel = ChatModel +private fun filteredChats(showUnreadAndFavorites: Boolean, searchText: String, chats: List): List { val s = searchText.trim().lowercase() return if (s.isEmpty() && !showUnreadAndFavorites) - chatModel.chats + chats else { - chatModel.chats.filter { chat -> + chats.filter { chat -> when (val cInfo = chat.chatInfo) { is ChatInfo.Direct -> if (s.isEmpty()) { filtered(chat)