-
Notifications
You must be signed in to change notification settings - Fork 768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Timeline recycling crash #4790
Timeline recycling crash #4790
Conversation
} else { | ||
holder.messageView.text = if (bindingOptions?.preventMutation.orFalse()) { | ||
setTextFuture(null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line is the potential fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, will see if we have still the crash after!
super.bind(holder) | ||
holder.messageView.movementMethod = movementMethod | ||
renderSendState(holder.messageView, holder.messageView) | ||
holder.messageView.onClick(attributes.itemClickListener) | ||
holder.messageView.onLongClickIgnoringLinks(attributes.itemLongClickListener) | ||
|
||
message?.let { holder.messageView.setTextWithEmojiSupport(it, bindingOptions) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that message
should never be null, but the change is not strictly equivalent to what we had before: message ?: ""
.
Maybe continue to fallback to empty string, to ensure that the text is always updated, or change message
to a lateinit CharSequence
(if message is null, the TextView content will not be reset and will display the previous bound value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah good catch, resetting to null seems safer, I don't know the epoxy lifecycle well enough to trust using a late init
- tentatively fixes IllegalArgumentException when recycling the text views due to AppCompatTextView.consumeTextFutureAndSetBlocking attempting to consuming any futures, even if they may be invalid
…recycling and an item doesn't contain a message
b5ce8dc
to
6380ee9
Compare
} else { | ||
null | ||
} | ||
markwonPlugins?.forEach { plugin -> plugin.beforeSetText(holder.messageView, message?.charSequence as Spanned) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line (markwonPlugins...
) has been removed by mistake during the merge I guess
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah 🤦 I got confused by https://github.com/vector-im/element-android/pull/4790/files#diff-473563ebe7dac7363901c094cc094bb438921f76a16bf13a020eefbff15d0c24R100 didn't realise there were two
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are 2 different methods beforeSetText
and a afterSetText
, I have no idea what they are doing TBH
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from the default implementations in the markwon library, it seems they're mainly used for apply text movements (links) and measuring lists but custom plugins can make use of them if needed
/**
* It can be useful to prepare a TextView for markdown. For example {@code ru.noties.markwon.image.ImagesPlugin}
* uses this method to unregister previously registered {@link AsyncDrawableSpan}
* (if there are such spans in this TextView at this point). Or {@link CorePlugin}
* which measures ordered list numbers
**/
@@ -92,27 +91,22 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() { | |||
it.bind(holder.messageView) | |||
} | |||
} | |||
val textFuture = if (bindingOptions?.canUseTextFuture.orFalse()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering why we coputed that before the next line... Maybe there were a good reason, I do not remember
} else { | ||
null | ||
} | ||
markwonPlugins?.forEach { plugin -> plugin.beforeSetText(holder.messageView, message?.charSequence as Spanned) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also noticed message?.charSequence as Spanned
will throw if the message is null, will wrap with a .let
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm pretty sure message
cannot be null but you are right, better safe than sorry.
… a nullable check to avoid attempt to cast a null to non null
Tentatively fixes #4789 Timeline crashes when quickly scrolling or restoring state.
I was unable to reproduce this crash locally, the stack trace looks to be the same as when a
TextView
is modified after setting aTextFuture
My theory is that
RecyclerView
is recyclingTextViews
with non consumed futures which causes them to throw when theTextView
options are rebound. This PR aims to solve the issue by resetting any present futures before callingsetText
which will allowAppCompatTextView.consumeTextFutureAndSetBlocking
to be skipped if the view is rebound withsetText
directly (such is the case when emojis are present)