linkedin / Spyglass

A library for mentions on Android
Apache License 2.0
387 stars 128 forks source link

How to persist MentionsEditable to edit later #35

Closed ClarkXP closed 7 years ago

ClarkXP commented 8 years ago

I'm working in a app that must save drafts texts to edit them later. How can I persist this text and restore it, without lost the mentions?

AhmedvHashem commented 8 years ago

you can make something like this

MentionsEditText text;
//your MentionsEditText setup here (Tokenizer, etc...)

text.setText("your full text here that include Tags too");
for (UserTag tag : taggedUsers)
{
    int si = TextUtils.indexOf(text.getText(), tag.UserName);
    int ei = si + tag.UserName.length();
    if (si >= 0)
    {
        text.getText().replace(si, ei, "");
        Selection.setSelection(text.getText(), si);
        text.insertMention(tag);
    }
}
sivaraj-dev commented 7 years ago

How can I do the same thing for RichEditorView

sivaraj-dev commented 7 years ago

Can you pls help me with this

nhibner commented 7 years ago

First, note that there is not really any difference between using a RichEditorView and a MentionsEditText here. Note that RichEditorView is just a custom view that wraps a MentionsEditText to make basic use cases easier to handle. In both cases, the text inside the view is represented by a MentionsEditable.

A similar use case we already handle is saving and restoring the text when your activity is destroyed and recreated. We do this by implementing the Parcelable interface in the MentionsEditable. You could try to reuse this mechanism (i.e. call onSaveInstanceState() and persist the parcelable to disk, then restore it later via onRestoreInstanceState()), but this is not recommended (see the docs for Parcel, which point out that it should not be used since changes in the underlying implementation of data in the Parcel can render older data unreadable).

Other than preserving the view state via the Android system callbacks, the library itself does not handle generically persisting this text. This was intentional. There are a lot of different use cases (i.e. persisting to a disk/cache, database, or over the network) and formats for this data (JSON, XML, binary formats, etc.). In this case, you'll need to implement something similar to what @AhmedHashemNTS suggested above. The basic idea is that you need to persist both the text and the spans (specifically, the starting index, the length, and any custom data within the span).

I'll walk-through a concrete example. Let's say that you decide to model the mentions data you want to persist with a schema like this. Note that I'm using plain POJOs here, but this would be whatever data structure you would use to handle persisting (i.e. you could also use JSONObject for transmitting over the network or use tables/rows in a database):

class MentionsTextData {
    String text;
    List<Mentions> mentions;
}

class Mention {
    int startIndex;
    int endIndex;
}

class EntityMention extends Mention {
    int entityId;
}

Note that for this example, all of our MentionSpans will use the following concrete implementation of the Mentionable interface. I'm including this just to help showcase how you can handle custom, arbitrary data.

class MentionableEntity implements Mentionable {
    // Any mention span will reference some entity (i.e. a person or company) with this id
    int entityId;
}

Now to save this data, you will need to write a function to convert a MentionsEditable to a MentionsTextData (where the MentionsTextData is just an example POJO class that you can persist to disk, a database, send over the network, etc.). This would look something like:

// Get text from a RichEditorView and convert to MentionsTextData
MentionsTextData data = toMentionsTextData(richEditorView.getText());

@NonNull
public MentionsTextData toMentionsTextData(@NonNull MentionsEditable editable) {
    MentionsTextData data = new MentionsTextData();
    data.text = editable.toString();
    data.mentions = new ArrayList<>();
    List<MentionSpan> spans = getMentionSpans();
    for (MentionSpan span : spans) {
        EntityMention mention = new EntityMention();
        mention.startIndex = editable.getSpanStart(span);
        mention.endIndex = editable.getSpanEnd(span);
        mention.entityId = ((MentionableEntity) span.mentionable).entityId;
        data.mentions.add(mention);
    }
    return data;
}

To restore the data, you will need to write the reverse function to convert this MentionsTextData that was persisted into a MentionsEditable, then assign it to the RichEditorView. This would look something like:

richEditorText.setText(toMentionsEditable(mentionsTextData));

@NonNull
public MentionsEditable toMentionsEditable(@NonNull MentionsTextData data) {
    MentionsEditable editable = new MentionsEditable(data.text);
    for (Mention mention : data.mentions) {
        // You'll need to be able to recreate the MentionSpan from the data, including any arbitrary data
        // Here, we just make sure we correct restore our custom entityId field
        EntityMentionable mentionable = new EntityMentionable();
        mentionable.entityId = mention.entityId;
        MentionSpan span = new MentionSpan(mentionable);
        ediable.setSpan(span, mention.startIndex, mention.endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    return editable;
}

Hope this helps. Let me know if you have any questions :)