Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting text on multiple inflated EditTexts causes all to be populated with same text after rotation

I am inflating a few EditTexts and adding them to a LinearLayout:

private void setupCommentViews(){
    int i = 0;
        Iterator<CommentInformation> iterator = commentInformations.iterator();
    while(iterator.hasNext()) {
            i++;
            View v = LayoutInflater.from(context).inflate(R.layout.comment_row_item, commentsContainer, false);

            EditText commentField = (EditText)v.findViewById(R.id.comment);         

            CommentInformation commentInformation = iterator.next();
            final String uniqueId = commentInformation.getUniqueId();

            String currentCommentText = comments.get(uniqueId);         
            if(currentCommentText != null) {
                Log.v("into","Setting old text: "+currentCommentText);
                commentField.setText(currentCommentText);
            } else {
                Log.v("into","Setting empty text: "+i);
                commentField.setText("Setting empty text: "+i);
            }


            commentField.addTextChangedListener(new TextWatcher() {

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                }

                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {

                }

                @Override
                public void afterTextChanged(Editable s) {
                    comments.put(uniqueId, s.toString().trim());
                }
            });

            commentsContainer.addView(v);
        }
}

The logs after the first run look like this:

04-01 17:40:41.244: V/into(28770): Setting empty text: 1
04-01 17:40:41.254: V/into(28770): Setting empty text: 2
04-01 17:40:41.254: V/into(28770): Setting empty text: 3

And all of the EditTexts have the correct text in them ("Setting empty text: #")

-

I am adding a TextChangedListener to my EditText that I inflate and when the text is changed I update a Map<String, String> and put the uniqueId as the key and the comment text as the value.

The problem occurs when I rotate the phone after changing one of the comments. I save the comments Map inside of onSaveInstanceState:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable(COMMENTS, (Serializable)comments);
}

and I then retrieve them inside of onActivityCreated

if(savedInstanceState != null) {
        Log.v("into","Retrieving...");
        comments = (Map<String, String>)savedInstanceState.getSerializable(COMMENTS);
}

I also iterate through that Map and verify that there is one entry with the correct uniqueId and correct changed comment text.

-

Next I call setupCommentViews() again. This time the logs look right, but the EditTexts ALL have the same text as the final one (the last inflated one).

04-01 17:42:49.664: V/into(28770): Setting empty text: 1
04-01 17:42:49.664: V/into(28770): Setting old text: Changed the second textview
04-01 17:42:49.674: V/into(28770): Setting empty text: 3

But all the EditTexts now say: "Setting empty text: 3"

Using a ListView is NOT the solution I'm looking for as there are lots of bugs with EditTexts inside of ListView with AdjustResize softInputMode

-

Can anyone shed any light on what is going on here?

like image 712
pat Avatar asked Apr 02 '14 00:04

pat


4 Answers

I met this problem too, but I have to restore the sate before. So i create a View named FixedEditText and override both dispatchSaveInstanceState() and dispatchRestoreInstanceState() two method. just like this:

 override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
    try {
        val key = initKey()
        if (key != null && isSaveEnabled) {
            val state = onSaveInstanceState()
            if (state != null) {
                //don't use the id as the key
                container.put(key.hashCode(), state)
            }
        }
    } catch (e: Exception) {
        super.dispatchSaveInstanceState(container)
        e.printStackTrace()
    }
}

override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
    try {
        val key = initKey()
        if (key != null) {
            val state = container.get(key.hashCode())
            if (state != null) {
                onRestoreInstanceState(state)
            }
        }
    } catch (e: Exception) {
        super.dispatchRestoreInstanceState(container)
        e.printStackTrace()
    }
}
 private fun initKey(): Any? {
    (parent as? View)?.run {
        //My ViewGroup has diff id.
        return "${this.javaClass.simpleName}$${this.id}"
    }
    return null
}
like image 189
joe Avatar answered Oct 18 '22 21:10

joe


By default, when onSaveInstanceState method of an activity is called, it also goes through the view hierarchy and try to save the state of any view if possible and restore those views state if onRestoreInstanceState method of activity is called later. When each View is called to save the state, we can see this method in the View class:

protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
            Parcelable state = onSaveInstanceState();
            if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                throw new IllegalStateException(
                        "Derived class did not call super.onSaveInstanceState()");
            }
            if (state != null) {
                // Log.i("View", "Freezing #" + Integer.toHexString(mID)
                // + ": " + state);
                container.put(mID, state);
            }
        }
    }

EditText inherits from TextView, when check the code of TextView we see the onSaveInstanceState method return current text and onRestoreInstanceState method get back the text and display.

Now, back to your code. You save the text content yourself and restore it when onCreate is called, but EditText itself also save the text and restore when onRestoreInstanceState method of Activity is call, which is called after onCreate for sure, one more thing, all your EditText have same id, so when the state of view hierarchy is saved in to a SparseArray, the last EditText state (in this case is text content) will overwrite all the previous. And that's why your problem happen.

Solutions:

  1. You can override method onRestoreInstanceState of activity and not call super method so that you can prevent the views to auto-restore its state, but I don't think this is good idea since there might be other things need to be restored by default, not only your EditText
  2. You can set the attribute saveEnabled of EditText to false so that it will not restore the state itself, I recommend this.
like image 19
Binh Tran Avatar answered Nov 06 '22 07:11

Binh Tran


I had the same problem. In my custom LinearLayout I Override these methods.

   @Override
protected Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    return new SavedState(superState, txtData.getText().toString());
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    SavedState savedState = (SavedState) state;
    super.onRestoreInstanceState(savedState.getSuperState());
    txtData.setText(savedState.data);
}

@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    super.dispatchFreezeSelfOnly(container);
}

@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    super.dispatchThawSelfOnly(container);
}

private class SavedState extends BaseSavedState {
    public final Creator<SavedState> CREATOR = new Creator<SavedState>() {
        @Override
        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        @Override
        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
    private String data;

    public SavedState(Parcel source) {
        super(source);
        data = source.readString();
    }

    public SavedState(Parcelable superState, String data) {
        super(superState);
        this.data = data;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString(data);
    }
}

Hope this helps.

like image 2
Yogitha Bellare Avatar answered Nov 06 '22 07:11

Yogitha Bellare


EditText restores text based on resId. If your edittexts don't have resIds or all have the same resIds you'll have restore problems.

like image 1
MinceMan Avatar answered Nov 06 '22 07:11

MinceMan