0

I am trying to figure out how to limit an edit text field to 140 twitter characters, without allowing the user to type over the limit. I thought I had it working with the code below, however, I discovered it only prevents typing if the cursor is at the end of the editText field. As much as I have tried, I cannot figure out the logic of source and dest, and how to concatenate them into the complete string that would be shown in the editText field. This is what I was attempting to do the the fullTextString.

Note, this is not as simple as limiting the length of the edit text field. Twitter considers a link such as "t.co" as 22 characters, not 3. I haven't seen any other working examples.

A full example to quickly clone is on github here: https://github.com/tylerjroach/TwitterEditTextLengthFilter

public class TwitterLengthFilter implements InputFilter {
Validator tweetValidator = new Validator();
private final int max;

public TwitterLengthFilter(int max) {
    this.max = max;
}

public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
                           int dstart, int dend) {

    String destString = dest.subSequence(0, dstart).toString();
    String sourceString = source.subSequence(start, end).toString();
    String fullTextString = destString + sourceString;

    if (fullTextString.length() == 0) {
        return "";
    } else if (tweetValidator.getTweetLength(fullTextString) <= max) {
        return null; //keep original
    } else {
        CharSequence returnSource = "";
        for (int i=0; i<sourceString.length(); i++) {
            if (tweetValidator.getTweetLength(destString + source.subSequence(0, i + 1)) <= max) {
                returnSource = source.subSequence(0, i + 1);
            }
        }
        return returnSource;
    }
}

}

tylerjroach
  • 2,699
  • 4
  • 19
  • 24

2 Answers2

0

Try adding this to your edit text XML:

android:maxLength="140"
Tyler Kiser
  • 921
  • 8
  • 14
  • As I said above "Note, this is not as simple as limiting the length of the edit text field. Twitter considers a link such as "t.co" as 22 characters, not 3. I haven't seen any other working examples." Most implementations do this, which is incorrect. Just doing this will fail in any situations where there is a link. – tylerjroach Jun 10 '15 at 20:00
  • you can try TextWatcher and in onTextChanged of TextWatcher if there is any link, recalculate the maxLength of the edittext. – Vivart Jun 10 '15 at 20:15
  • Hmm, I would assume onTextChanged is too late though. If I am correct, the filtering should happen before anything hits the textWatcher. Attempting to remove characters in the textWatcher (if the limit was hit) will create an endless loop. – tylerjroach Jun 10 '15 at 20:22
  • Then use beforeTextChanged – Tyler Kiser Jun 10 '15 at 20:28
  • I just checked and was able to verify that the TextFilter runs before "beforeTextChanged" is called. Modifying the text in beforetextchanged will create an endless loop. See here: http://stackoverflow.com/questions/476848/android-textwatcher-aftertextchanged-vs-textwatcher-ontextchanged. I'm still attempting to work with the InputFilter documentation to figure out exactly what needs to be done. – tylerjroach Jun 10 '15 at 20:38
  • Ah, I just figured it out with the TextFilter. I'll post with the answer in a moment. – tylerjroach Jun 10 '15 at 20:49
0

After reading more into the filter method I was finally able to figure it out. I'm sure it could be more efficient, but it works for now. I would love any suggestions to make it better.

This method is called when the buffer is going to replace the range dstart … dend of dest with the new text from the range start … end of source. Return the CharSequence that you would like to have placed there instead, including an empty string if appropriate, or null to accept the original replacement. Be careful to not to reject 0-length replacements, as this is what happens when you delete text. Also beware that you should not attempt to make any changes to dest from this method; you may only examine it for context. Note: If source is an instance of Spanned or Spannable, the span objects in the source should be copied into the filtered result (i.e. the non-null return value). copySpansFrom(Spanned, int, int, Class, Spannable, int) can be used for convenience.

public class TwitterLengthFilter implements InputFilter {
Validator tweetValidator = new Validator();
private final int max;

public TwitterLengthFilter(int max) {
    this.max = max;
}

public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
                           int dstart, int dend) {

    StringBuilder destination = new StringBuilder(dest);
    String sourceString = source.subSequence(start, end).toString();
    String fullTextString = destination.replace(dstart, dend, sourceString).toString();

    Log.v("Full text", fullTextString);

    if (fullTextString.length() == 0) {
        return "";
    } else if (tweetValidator.getTweetLength(fullTextString) <= max) {
        return null; //keep original
    } else {
        CharSequence returnSource = "";
        for (int i=0; i<sourceString.length(); i++) {
            String iterateSource = source.subSequence(0, i + 1).toString();
            StringBuilder iteratedDestination = new StringBuilder(dest);
            iteratedDestination.replace(dstart, dend, iterateSource);
            if (tweetValidator.getTweetLength(iteratedDestination.toString()) <= max) {
                returnSource = source.subSequence(0, i + 1);
            }
        }
        return returnSource;
    }
}
tylerjroach
  • 2,699
  • 4
  • 19
  • 24