From 8b4051dcd0cd8e3941d48a72dfdc808a36f77542 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 7 Aug 2026 12:17:52 +0200 Subject: [PATCH] fix(chat): avoid ClassCastException when building FileParameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ChatMessageUi.messageParameters is built via Kotlin's toMap(), which returns a Collections.SingletonMap for a single entry instead of a HashMap. Both call sites unsafely cast it to HashMap>?, which crashes whenever a message has exactly one rich-object parameter (e.g. a plain file share) - the common case for FileParameters. Build real HashMap instances from messageParameters instead of casting it. Also removes a runCatching block in LazyListState.visibleItemsWithThreshold() that silently swallowed the same ClassCastException, which was hiding this bug and causing file id lookups to fail there. fixed crash: ❯ 2026-08-07 12:02:40.777 28629-28629 NextcloudT...pplication com.nextcloud.talk2 E Uncaught exception in thread "main" (Fix with AI) java.lang.ClassCastException: java.util.Collections$SingletonMap cannot be cast to java.util.HashMap at com.nextcloud.talk.ui.chat.MediaMessageKt.MediaMessage(MediaMessage.kt:71) at com.nextcloud.talk.ui.chat.ChatMessageViewKt.ChatMessageView$lambda$5$1(ChatMessageView.kt:151) at com.nextcloud.talk.ui.chat.ChatMessageViewKt.$r8$lambda$BiaHdWRFag7qJIfvY45CugWWbIY(ChatMessageView.kt:0) at com.nextcloud.talk.ui.chat.ChatMessageViewKt$$ExternalSyntheticLambda1.invoke(D8$$SyntheticClass:0) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52) at com.nextcloud.talk.ui.chat.SwipeToReplyContainerKt.SwipeToReplyContainer(SwipeToReplyContainer.kt:57) at com.nextcloud.talk.ui.chat.ChatMessageViewKt.ChatMessageView$lambda$5(ChatMessageView.kt:114) at com.nextcloud.talk.ui.chat.ChatMessageViewKt$$ExternalSyntheticLambda4.invoke(D8$$SyntheticClass:0) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52) at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:408) at com.nextcloud.talk.ui.chat.ChatMessageViewKt.ChatMessageView(ChatMessageView.kt:105) Co-Authored-By: Claude Sonnet 5 Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/chat/ChatActivity.kt | 15 +++++---------- .../com/nextcloud/talk/ui/chat/MediaMessage.kt | 8 +++++++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 879b9f398e..a2b5d8816b 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -1001,16 +1001,11 @@ class ChatActivity : var result: String? = null message?.let { if (message.messageParameters.isNotEmpty()) { - runCatching { - message.messageParameters as HashMap>? - val fileParameters = FileParameters(message.messageParameters) - result = fileParameters.id - }.onFailure { e -> - when (e) { - is ClassCastException -> {} // weird - else -> Log.e(TAG, "Error in LazyListState.visibleItemsWithThreshold $e") - } - } + val normalizedParameters = HashMap>( + message.messageParameters.mapValues { (_, params) -> HashMap(params) } + ) + val fileParameters = FileParameters(normalizedParameters) + result = fileParameters.id } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/chat/MediaMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/chat/MediaMessage.kt index d07efe2ca7..45df391c85 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/chat/MediaMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/chat/MediaMessage.kt @@ -68,7 +68,13 @@ fun MediaMessage( onImageClick: (Int) -> Unit ) { val fileParameters = - remember { FileParameters(message.messageParameters as HashMap>?) } + remember { + FileParameters( + HashMap( + message.messageParameters.mapValues { (_, params) -> HashMap(params) } + ) + ) + } val hasExplicitCaption = message.plainMessage != FILE_PLACEHOLDER_MESSAGE val hasPreview = !typeContent.previewUrl.isNullOrEmpty()