-
Notifications
You must be signed in to change notification settings - Fork 65
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
Fix removeSpan
crash on Android
#469
Fix removeSpan
crash on Android
#469
Conversation
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
I have read the CLA Document and I hereby sign the CLA |
recheck |
@wildan-m Thanks for the PR! I will review it soon. edit: I believe there was a reason why |
@tomekzaw Sure, thank you. Another option could be to include try-catch blocks within android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java private void setSpan(SpannableStringBuilder ssb, MarkdownSpan span, int start, int end) {
try {
ssb.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
Log.e("MarkdownUtils", "Error setting span", e);
}
}
private void removeSpans(SpannableStringBuilder ssb) {
// We shouldn't use `removeSpans()` because it also removes SpellcheckSpan, SuggestionSpan etc.
MarkdownSpan[] spans = ssb.getSpans(0, ssb.length(), MarkdownSpan.class);
for (MarkdownSpan span : spans) {
try {
ssb.removeSpan(span);
} catch (Exception e) {
Log.e("MarkdownUtils", "Error removing span: " + span, e);
}
}
} |
Thanks again for the PR! I think we can go even one more step further and get rid of @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// do nothing
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// do nothing
}
@Override
public void afterTextChanged(Editable editable) {
if (editable instanceof SpannableStringBuilder) {
mMarkdownUtils.applyMarkdownFormatting((SpannableStringBuilder) editable);
}
} I've tested out this change and it seems to work correctly both on Fabric and Paper. FYI Another option suggested by @j-piasecki would be to wrap private void removeSpans(SpannableStringBuilder ssb) {
// We shouldn't use `removeSpans()` because it also removes SpellcheckSpan, SuggestionSpan etc.
MarkdownSpan[] spans = ssb.getSpans(0, ssb.length(), MarkdownSpan.class);
for (MarkdownSpan span : spans) {
try {
ssb.removeSpan(span);
} catch (Exception e) {
// do nothing
}
}
} Let's proceed with the first suggestion, i.e. removing |
@tomekzaw Will we get duplicate calls without the flag? |
@tomekzaw I see why On each change:
Is it acceptable to use the second solution to ensure edit: I think I've found a new approach that might be better. We can call if (mPreviousEditable != null && mPreviousEditable.toString().equals(editable.toString())) {
return;
} Solution 3 - Compare prevValuepackage com.expensify.livemarkdown;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import androidx.annotation.NonNull;
public class MarkdownTextWatcher implements TextWatcher {
private final MarkdownUtils mMarkdownUtils;
private boolean mShouldSkip = false;
private Editable mPreviousEditable;
public MarkdownTextWatcher(@NonNull MarkdownUtils markdownUtils) {
mMarkdownUtils = markdownUtils;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable editable) {
if (editable instanceof SpannableStringBuilder) {
if (mPreviousEditable != null && mPreviousEditable.toString().equals(editable.toString())) {
return;
}
System.out.println("[wildebug] applyMarkdownFormatting editable: " + editable);
mMarkdownUtils.applyMarkdownFormatting((SpannableStringBuilder) editable);
mPreviousEditable = new SpannableStringBuilder(editable);
}
}
} |
@tomekzaw bump |
@wildan-m I was curious why One user suggested that setting As for the solution with As for As for the solution with comparing the previous value ( |
…markdown into wildan/fix/46908-delete-markdown-crash
// Reset the flag after formatting is applied | ||
mShouldSkip = false; | ||
}} | ||
mPreviousEditable = new SpannableStringBuilder(editable); |
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.
Do we need to store mPreviousEditable
as a SpannableStringBuilder
? We just use mPreviousEditable.toString()
so maybe let's keep it as a string?
How about mPreviousText
?
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.
That's make sense, please wait
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.
done
@tomekzaw Thanks for your review. The PR has been updated with unnecessary code removed. |
android/src/main/java/com/expensify/livemarkdown/MarkdownTextWatcher.java
Outdated
Show resolved
Hide resolved
} | ||
} | ||
} |
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.
Let's restore the trailing newline
@wildan-m I've left some final suggestions |
…atcher.java Co-authored-by: Tomek Zawadzki <[email protected]>
android/src/main/java/com/expensify/livemarkdown/MarkdownTextWatcher.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/expensify/livemarkdown/MarkdownTextWatcher.java
Outdated
Show resolved
Hide resolved
@@ -8,8 +8,9 @@ | |||
|
|||
public class MarkdownTextWatcher implements TextWatcher { | |||
private final MarkdownUtils mMarkdownUtils; | |||
|
|||
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.
…atcher.java Co-authored-by: Tomek Zawadzki <[email protected]>
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.
Thanks for fixing this! I will merge this PR soon.
removeSpan
on Android
removeSpan
on AndroidremoveSpan
crash on Android
@wildan-m During testing I found one regression – regression.movThis would cause a regression in E/App when toggling light/dark mode. I was able to fix this by resetting @Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mPreviousText = null;
} This could be also performed in Here's the correct behavior with this change: correct.movAlternatively, we could check if edit: Finally, we can just get rid of this optimization at all: @Override
public void afterTextChanged(Editable editable) {
if (editable instanceof SpannableStringBuilder) {
- String currentText = editable.toString();
- if (currentText.equals(mPreviousText)) {
- return;
- }
mMarkdownUtils.applyMarkdownFormatting((SpannableStringBuilder) editable);
- mPreviousText = currentText;
}
} |
@@ -10,6 +10,7 @@ public class MarkdownTextWatcher implements TextWatcher { | |||
private final MarkdownUtils mMarkdownUtils; | |||
|
|||
private boolean mShouldSkip = false; |
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.
Let's also remove this field as it's no longer used.
@tomekzaw is it ok to call the apply markdown four times? #469 (comment)
checking the regression issue |
@tomekzaw I believe using a This issue typically requires a debouncer, but it may not be relevant in this case. Here is the result: Kapture.2024-09-11.at.20.25.49.mp4What are your thoughts? is it ok to move with |
@wildan-m I think we can call Also, have you checked my suggestion with resetting I'm quite astonished with how debouncing works – I thought debouncing would be a poor user experience but this doesn't look bad at all. However, one of the best features of live-markdown library is synchronous formatting – but maybe we could add debouncing as a new separate feature? Would you like to share the debouncing code with me in form of a patch or a draft PR? |
@tomekzaw resetting Kapture.2024-09-11.at.20.59.18.mp4
In that case, I suggest keeping the This is the debouncer snippet debounce solutionpackage com.expensify.livemarkdown;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import androidx.annotation.NonNull;
public class MarkdownTextWatcher implements TextWatcher {
private final MarkdownUtils mMarkdownUtils;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final long DEBOUNCE_DELAY = 100; // 300 milliseconds delay
private Runnable mRunnable;
public MarkdownTextWatcher(@NonNull MarkdownUtils markdownUtils) {
mMarkdownUtils = markdownUtils;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(final Editable editable) {
if (editable instanceof SpannableStringBuilder) {
if (mRunnable != null) {
mHandler.removeCallbacks(mRunnable);
}
mRunnable = new Runnable() {
@Override
public void run() {
System.out.println("[wildebug] applyMarkdownFormatting");
mMarkdownUtils.applyMarkdownFormatting((SpannableStringBuilder) editable);
}
};
mHandler.postDelayed(mRunnable, DEBOUNCE_DELAY);
}
}
} |
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.
Regression found, not ready to be merged yet
@tomekzaw the optimization has been removed |
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.
@wildan-m Thanks again for your PR. This is now ready to merge. Let's focus on performance improvements in a separate PR.
Regression fixed on Android ✅ Screen.Recording.2024-09-12.at.09.37.04.mov |
Issue fixed ✅ Screen.Recording.2024-09-12.at.09.38.51.mov |
No regressions with height calculation while typing ✅ (content jumps on newlines also happen on Screen.Recording.2024-09-12.at.09.40.19.mov |
Details
When applying formatting, there may be a race condition if we are trying to remove a span that has already been removed.
Related Issues
Expensify/App#46908
Manual Tests
Kapture.2024-09-03.at.14.03.01.mp4
Linked PRs