Search code examples
javaandroidclonespannablestring

Clone SpannableStringBuilder


I am working on an Activity in which I parse a text with markup characters. What I'm doing is converting them to several types of ClickableSpans.

The problem is that I need to implement a function (lets call it function B) that implies having overlapping ClickableSpans and that causes several issues.

So what I'm doing now is creating a new SSB while detecting the overlapping spans, and removing the ones I don't need for this particular function. Working fine.

BUT, I need to be able to go back to the previous SSB and that doesn't seem to work.

STEP BY STEP:

// I CREATE THE SSBs
...
static SpannableStringBuilder ssb;
static SpannableStringBuilder ssbCopy;

// I IMPLEMENT MY CUSTOM FUNCTION THAT PARSES THE TEXT AND SETS THE SBB AS A TEXTVIEW CONTENT
...
textView.setMovementMethod(new LinkTouchMovementMethod());
ssb = addClickablePart(chapterTextStr, markupCharactersArray);
textView.setText(ssb);

// WHEN A BUTTON IS CLICKED I IMPLEMENT MY FUNCTION B. WHERE I CREATE A COPY OF MY ORIGINAL SSB AND STORE IT IN ssbCopy, AND SET IT AS THE TEXTVIEW CONTENT
...
ssbCopy = SpannableStringBuilder.valueOf(ssb);

// I REMOVE THE OVERLAPPING SPANS
...
overlapSpans = ssbCopy.getSpans(index, index+word.length(), TouchableSpan.class);

for (int c=0;c<overlapSpans.length;c++) {                       
    ssbCopy.removeSpan(overlapSpans[c]);
}                       

// I SET THE NEW CLICKABLE SPANS
...
ssbCopy.setSpan(touchableSpan, index, index + word.length(), 0);

// AND SET THE NEW SSB CONTENT TO THE TEXTVIEW
textView.setText(ssbCopy);

// EVERYTHING WORKS FINE UP TO HERE
// BUT WHEN I TRY TO SET BACK THE ORIGINAL SSB BACK AS THE CONTENT OF MY TEXTVIEW WHEN THE USER CLICKS A BUTTON
...
textView.setText(ssb);

// THE ORIGINAL SSB IS EXACTLY LIKE THE COPY (ssbCopy) AND CONTAINS THE SAME CLICKABLE SPANS I ADDED. NOT ONLY THE ORIGINAL ONES

I guess it may sound somewhat confusing and I'm not sure if I explained properly, but I can't get around this.

EDIT:

As per kcoppock answer, I learn it isn't possible to clone an ssb and valueOf(ssb) is just a copy of the object. So I ended up cloning my "ssb" manually by looping through all elements and applying them to the new ssb. Like this:

TouchableSpan[] spans = ssb.getSpans(0, ssb.length(), TouchableSpan.class);

ssbCopy = new SpannableStringBuilder(chapterTextStr+"dsadsa");

for (int c=0;c<spans.length;c++) {
    TouchableSpan obj = spans[c];

    ssbCopy.setSpan(obj, ssb.getSpanStart(obj), ssb.getSpanEnd(obj), 0);
}

By the way, TouchableSpan is a custom class I created that extends ClickableSpan


Solution

  • The problem here is your use of valueOf(). It doesn't do what you think it does. All it does is return the object passed in, if it is a SpannableStringBuilder; otherwise, it wraps the given CharSequence in a SpannableStringBuilder. From the source:

    public static SpannableStringBuilder valueOf(CharSequence source) {
        if (source instanceof SpannableStringBuilder) {
            return (SpannableStringBuilder) source;
        } else {
            return new SpannableStringBuilder(source);
        }
    }
    

    So essentially:

    ssb == SpannableStringBuilder.valueOf(ssb);
    

    They are one and the same Object. SpannableStringBuilder does not implement Cloneable, so there's no easy way to make a copy, other than to just generate two copies, for example:

    ssb = addClickablePart(chapterTextStr, markupCharactersArray);
    ssbCopy = addClickablePart(chapterTextStr, markupCharactersArray);