From cead6ae730912d6e7bb09f17af5a8020f234e5b8 Mon Sep 17 00:00:00 2001 From: Kanat Kiialbaev Date: Fri, 22 Mar 2024 15:34:20 -0700 Subject: [PATCH] (crash) fix message list crash (#5219) * (crash) fix message list crash * code clean up * detekt, spotless, api dump * add CHANGELOG * code clean up --- CHANGELOG.md | 1 + .../api/stream-chat-android-ui-components.api | 1 + .../detekt-baseline.xml | 1 - .../list/adapter/BaseMessageItemViewHolder.kt | 4 +++ .../MessageListItemViewHolderFactory.kt | 35 ++++++++++++++----- .../list/adapter/MessageListItemViewType.kt | 23 ++++++++++++ .../internal/MessageListItemAdapter.kt | 19 ++++++++-- .../viewholder/internal/EmptyViewHolder.kt | 30 ++++++++++++++++ 8 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/internal/EmptyViewHolder.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 6380951ba1c..4c67d9ab518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ ## stream-chat-android-ui-components ### 🐞 Fixed - Fixed the crash happening while editing a message with a recording attachment. [#5220](https://github.com/GetStream/stream-chat-android/pull/5220) +- Fixed intermittent crash when opening a channel. [#5219](https://github.com/GetStream/stream-chat-android/pull/5219) ### ⬆️ Improved diff --git a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api index e3911725512..0925601b465 100644 --- a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api +++ b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api @@ -2779,6 +2779,7 @@ public final class io/getstream/chat/android/ui/feature/messages/list/adapter/Me public static final field THREAD_SEPARATOR I public static final field TYPING_INDICATOR I public static final field UNREAD_SEPARATOR I + public final fun toString (I)Ljava/lang/String; } public abstract interface class io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainer { diff --git a/stream-chat-android-ui-components/detekt-baseline.xml b/stream-chat-android-ui-components/detekt-baseline.xml index 55b90afdb3b..a72b293b700 100644 --- a/stream-chat-android-ui-components/detekt-baseline.xml +++ b/stream-chat-android-ui-components/detekt-baseline.xml @@ -153,7 +153,6 @@ NestedBlockDepth:MessageListHeaderView.kt$MessageListHeaderView$private fun renderSubtitleState() ParameterListWrapping:MessageComposerViewModelBinding.kt$MessageComposerViewModelDefaults$(Attachment) ParameterListWrapping:MessageComposerViewModelBinding.kt$MessageComposerViewModelDefaults$(Boolean) - RethrowCaughtException:BaseMessageItemViewHolder.kt$BaseMessageItemViewHolder$throw e ReturnCount:AttachmentDestination.kt$AttachmentDestination$public fun showAttachment(message: Message, attachment: Attachment) ReturnCount:ChatFontsImpl.kt$ChatFontsImpl$private fun getFont(@FontRes fontRes: Int): Typeface? ReturnCount:ChatFontsImpl.kt$ChatFontsImpl$private fun getFont(fontPath: String): Typeface? diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/BaseMessageItemViewHolder.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/BaseMessageItemViewHolder.kt index 49e5f527afc..14d023c048e 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/BaseMessageItemViewHolder.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/BaseMessageItemViewHolder.kt @@ -22,6 +22,7 @@ import android.view.View import androidx.annotation.CallSuper import androidx.recyclerview.widget.RecyclerView import io.getstream.chat.android.ui.common.internal.animateHighlight +import io.getstream.log.taggedLogger /** * Base ViewHolder used for displaying messages in @@ -31,6 +32,8 @@ public abstract class BaseMessageItemViewHolder( itemView: View, ) : RecyclerView.ViewHolder(itemView) { + private val logger by taggedLogger("Chat:MessageItemBaseVH") + /** * The data that was last bound to this ViewHolder via [bindData]. * Can be used for listeners that need to pass along the currently @@ -59,6 +62,7 @@ public abstract class BaseMessageItemViewHolder( try { bindData(messageListItem, diff) } catch (e: Throwable) { + logger.e(e) { "[bindListItem] failed: $e" } throw e } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemViewHolderFactory.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemViewHolderFactory.kt index 6a76fa29d8b..f0e62881a5f 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemViewHolderFactory.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemViewHolderFactory.kt @@ -16,7 +16,6 @@ package io.getstream.chat.android.ui.feature.messages.list.adapter -import android.view.View import android.view.ViewGroup import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.feature.messages.common.AudioRecordPlayerViewStyle @@ -53,6 +52,7 @@ import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.imp import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.MediaAttachmentsViewHolder import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.MessageDeletedViewHolder import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.impl.MessagePlainTextViewHolder +import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.internal.EmptyViewHolder import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.internal.ErrorMessageViewHolder import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.internal.LoadingMoreViewHolder import io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.internal.SystemMessageViewHolder @@ -174,6 +174,27 @@ public open class MessageListItemViewHolderFactory { return MessageListItemViewTypeMapper.getViewTypeValue(item, attachmentFactoryManager) } + internal fun getItemViewType(viewHolder: BaseMessageItemViewHolder): Int { + return when (viewHolder) { + is DateDividerViewHolder -> DATE_DIVIDER + is MessageDeletedViewHolder -> MESSAGE_DELETED + is MessagePlainTextViewHolder -> PLAIN_TEXT + is CustomAttachmentsViewHolder -> CUSTOM_ATTACHMENTS + is LoadingMoreViewHolder -> LOADING_INDICATOR + is ThreadSeparatorViewHolder -> THREAD_SEPARATOR + is GiphyViewHolder -> GIPHY + is SystemMessageViewHolder -> SYSTEM_MESSAGE + is ErrorMessageViewHolder -> ERROR_MESSAGE + is EmptyViewHolder -> viewHolder.viewType + is LinkAttachmentsViewHolder -> LINK_ATTACHMENTS + is GiphyAttachmentViewHolder -> GIPHY_ATTACHMENT + is FileAttachmentsViewHolder -> FILE_ATTACHMENTS + is MediaAttachmentsViewHolder -> MEDIA_ATTACHMENT + is UnreadSeparatorViewHolder -> UNREAD_SEPARATOR + else -> throw IllegalArgumentException("Unhandled MessageList view holder: $viewHolder") + } + } + /** * Creates a new ViewHolder to be used in the Message List. * The [viewType] parameter is determined by [getItemViewType]. @@ -189,17 +210,17 @@ public open class MessageListItemViewHolderFactory { CUSTOM_ATTACHMENTS -> createCustomAttachmentsViewHolder(parentView) LOADING_INDICATOR -> createLoadingMoreViewHolder(parentView) THREAD_SEPARATOR -> createThreadSeparatorViewHolder(parentView) - TYPING_INDICATOR -> createEmptyMessageItemViewHolder(parentView) + TYPING_INDICATOR -> createEmptyMessageItemViewHolder(parentView, viewType) GIPHY -> createGiphyMessageItemViewHolder(parentView) SYSTEM_MESSAGE -> createSystemMessageItemViewHolder(parentView) ERROR_MESSAGE -> createErrorMessageItemViewHolder(parentView) - THREAD_PLACEHOLDER -> createEmptyMessageItemViewHolder(parentView) + THREAD_PLACEHOLDER -> createEmptyMessageItemViewHolder(parentView, viewType) LINK_ATTACHMENTS -> createLinkAttachmentsViewHolder(parentView) GIPHY_ATTACHMENT -> createGiphyAttachmentViewHolder(parentView) FILE_ATTACHMENTS -> createFileAttachmentsViewHolder(parentView) MEDIA_ATTACHMENT -> createMediaAttachmentsViewHolder(parentView) UNREAD_SEPARATOR -> createUnreadSeparatorViewHolder(parentView) - START_OF_THE_CHANNEL -> createEmptyMessageItemViewHolder(parentView) + START_OF_THE_CHANNEL -> createEmptyMessageItemViewHolder(parentView, viewType) else -> throw IllegalArgumentException("Unhandled MessageList view type: $viewType") } } @@ -378,11 +399,9 @@ public open class MessageListItemViewHolderFactory { */ private fun createEmptyMessageItemViewHolder( parentView: ViewGroup, + viewType: Int, ): BaseMessageItemViewHolder { - return object : - BaseMessageItemViewHolder(View(parentView.context)) { - override fun bindData(data: MessageListItem, diff: MessageListItemPayloadDiff?) = Unit - } + return EmptyViewHolder(parentView, viewType) } /** diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemViewType.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemViewType.kt index b7a876cf058..05412bbfb9b 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemViewType.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemViewType.kt @@ -40,4 +40,27 @@ public object MessageListItemViewType { public const val LINK_ATTACHMENTS: Int = OFFSET + 15 public const val UNREAD_SEPARATOR: Int = OFFSET + 16 public const val START_OF_THE_CHANNEL: Int = OFFSET + 17 + + public fun toString(viewType: Int): String { + return when (viewType) { + DATE_DIVIDER -> "DATE_DIVIDER" + MESSAGE_DELETED -> "MESSAGE_DELETED" + PLAIN_TEXT -> "PLAIN_TEXT" + CUSTOM_ATTACHMENTS -> "CUSTOM_ATTACHMENTS" + LOADING_INDICATOR -> "LOADING_INDICATOR" + THREAD_SEPARATOR -> "THREAD_SEPARATOR" + TYPING_INDICATOR -> "TYPING_INDICATOR" + GIPHY -> "GIPHY" + SYSTEM_MESSAGE -> "SYSTEM_MESSAGE" + ERROR_MESSAGE -> "ERROR_MESSAGE" + THREAD_PLACEHOLDER -> "THREAD_PLACEHOLDER" + GIPHY_ATTACHMENT -> "GIPHY_ATTACHMENT" + MEDIA_ATTACHMENT -> "MEDIA_ATTACHMENT" + FILE_ATTACHMENTS -> "FILE_ATTACHMENTS" + LINK_ATTACHMENTS -> "LINK_ATTACHMENTS" + UNREAD_SEPARATOR -> "UNREAD_SEPARATOR" + START_OF_THE_CHANNEL -> "START_OF_THE_CHANNEL" + else -> "UNKNOWN" + } + } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/internal/MessageListItemAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/internal/MessageListItemAdapter.kt index 5434c1302a4..fecc9e67fa7 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/internal/MessageListItemAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/internal/MessageListItemAdapter.kt @@ -24,6 +24,7 @@ import io.getstream.chat.android.ui.feature.messages.list.adapter.BaseMessageIte import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItem import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItemPayloadDiff import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItemViewHolderFactory +import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItemViewType import io.getstream.log.taggedLogger internal class MessageListItemAdapter( @@ -42,7 +43,8 @@ internal class MessageListItemAdapter( override fun getItemId(position: Int): Long = getItem(position).getStableId() override fun getItemViewType(position: Int): Int { - return viewHolderFactory.getItemViewType(getItem(position)) + val item = getItem(position) + return viewHolderFactory.getItemViewType(item) } override fun onCreateViewHolder( @@ -53,7 +55,8 @@ internal class MessageListItemAdapter( } override fun onBindViewHolder(holder: BaseMessageItemViewHolder, position: Int) { - holder.bindListItem(getItem(position), FULL_MESSAGE_LIST_ITEM_PAYLOAD_DIFF) + val item = getItem(position) + holder.bindListItem(item, FULL_MESSAGE_LIST_ITEM_PAYLOAD_DIFF) } override fun onBindViewHolder( @@ -70,7 +73,17 @@ internal class MessageListItemAdapter( .fold(EMPTY_MESSAGE_LIST_ITEM_PAYLOAD_DIFF) { acc, messageListItemPayloadDiff -> acc + messageListItemPayloadDiff } - holder.bindListItem(getItem(position), diff) + val item = getItem(position) + val itemViewType = viewHolderFactory.getItemViewType(item) + val holderViewType = viewHolderFactory.getItemViewType(holder) + if (itemViewType != holderViewType) { + logger.w { + "[onBindViewHolder] viewType mismatch; item: ${MessageListItemViewType.toString(itemViewType)}" + + ", viewHolder: ${MessageListItemViewType.toString(holderViewType)}" + } + return + } + holder.bindListItem(item, diff) } override fun onViewRecycled(holder: BaseMessageItemViewHolder) { diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/internal/EmptyViewHolder.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/internal/EmptyViewHolder.kt new file mode 100644 index 00000000000..bb4a5f16597 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/internal/EmptyViewHolder.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.ui.feature.messages.list.adapter.viewholder.internal + +import android.view.View +import android.view.ViewGroup +import io.getstream.chat.android.ui.feature.messages.list.adapter.BaseMessageItemViewHolder +import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItem +import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItemPayloadDiff + +internal class EmptyViewHolder( + parentView: ViewGroup, + val viewType: Int, +) : BaseMessageItemViewHolder(View(parentView.context)) { + override fun bindData(data: MessageListItem, diff: MessageListItemPayloadDiff?) = Unit +}