Reputation: 2167
When I use onRetainCustomNonConfigurationInstance to save the text contained inside my custom EditTexts, after I rotate the device the text contained in the last EditText gets duplicated to all the others in the target layout.
While debugging with breakpoints, I found that all the text values are correct all the way. But after the changes are done, all the EditTexts get the same text showing (the text from the last one) and the focus is given to the first one in the layout.
I replicated this behavior in the simplest project I could. I tried with both android API levels 24 and 28.
Where does this behavior come from and how can I fix it ?
MainActivity.java :
public class MainActivity extends AppCompatActivity {
private ArrayList<CustomEdit> editList=new ArrayList<CustomEdit>();
private LinearLayout layout;
private Button addButton;
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addButton=findViewById(R.id.add_button);
layout = findViewById(R.id.layout);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addEdit();
}
});
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance();
if(data==null) return;
for(int i = 0; i<data.texts.size();i++){
addEdit(data.texts.get(i));
}
}
@Override
public Object onRetainCustomNonConfigurationInstance() {
CustomSave data = save();
return data;}
private CustomSave save(){
ArrayList<String> texts = new ArrayList<String>();
for(int i =0; i<editList.size(); i++)
texts.add(editList.get(i).getText());
return new CustomSave(texts);}
/**
* Create a new custom EditText with hint
*/
private void addEdit(){
CustomEdit newEdit = new CustomEdit(this,editList.size());
layout.addView(newEdit,editList.size());
editList.add(newEdit);}
/**
* Create a new custom editText with text
* @param text
*/
private void addEdit(String text){
CustomEdit newEdit;
if(text==null) newEdit = new CustomEdit(this, editList.size());
else newEdit = new CustomEdit(this, editList.size(),text);
layout.addView(newEdit,editList.size());
editList.add(newEdit);}
}
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="20dp"
android:text="Title"
android:gravity ="center"/>
<HorizontalScrollView
android:id="@+id/scroll1"
android:layout_below="@id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingLeft="10dp">
<LinearLayout
android:id="@+id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/add_button"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="+"/>
</LinearLayout>
</HorizontalScrollView>
</RelativeLayout>
CustomEdit.java :
public class CustomEdit extends RelativeLayout {
private EditText editText;
private Button closeButton;
private int indexNumber;
public CustomEdit(Context context, int indexNumber) {
super(context);
this.indexNumber =indexNumber;
init();
}
public CustomEdit(Context context, int indexNumber, String text){
super(context);
this.indexNumber=indexNumber;
init();
editText.setText(text);
}
public CustomEdit(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomEdit(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
init();
}
private void init(){
inflate(getContext(),R.layout.custom_edit_text,this);
editText = (EditText)findViewById(R.id.edit);
editText.setHint("EditText "+(indexNumber+1));
closeButton = (Button)findViewById(R.id.close_button);
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((MainActivity)getContext()).closeEdit(indexNumber);
}
});
}
public String getText(){
return editText.getText().toString();
}
}
custom_edit_text.xml :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="40dp">
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/edit"
android:inputType="text"
android:hint="Element"
android:maxLength="30"/>
<Button
android:id="@+id/close_button"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignRight="@id/edit"
android:layout_alignEnd="@id/edit"
android:layout_alignTop="@id/edit"
android:text="X"/>
</RelativeLayout>
CustomSave.java :
public class CustomSave {
public ArrayList<String> texts;
CustomSave(ArrayList<String> texts){
this.texts = texts;
}
}
Thank you.
Upvotes: 3
Views: 317
Reputation: 134664
So what's happening here is that Android is also handling state saving for your custom EditText
implementation, which is overwriting yours. Since your list of CustomEdit
instances is generated dynamically, you likely do not want to rely on Android to save the state for your views, since they do not have unique IDs.
Since your CustomEdit
inflates custom_edit_text.xml
(which declares the EditText
ID as @+id/edit
) that means that each CustomEdit
you add to the layout has the same ID for the inner EditText
-- R.id.edit
. Since they all have the same ID, each view will save its state to that ID, so the last one to save its state will end up being the text applied to all of the views when restoring state.
There are two things you can do to avoid this:
In your custom_edit_text.xml
, add android:saveEnabled="false"
to the EditText
. This will prevent that View
's state from being saved. This would be preferred as it avoids doing unnecessary work.
Perform your state restoration in onRestoreInstanceState()
where the view state is being restored currently.
Upvotes: 1
Reputation: 54204
You have two choices for how to solve this: move your code to a different point in the activity lifecycle or change the xml definition for CustomEdit
.
Move this code out of onCreate()
:
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance(); if(data==null) return; for(int i = 0; i<data.texts.size();i++){ addEdit(data.texts.get(i)); }
And put it in onRestoreInstanceState()
instead:
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
CustomSave data = (CustomSave)getLastCustomNonConfigurationInstance();
if(data==null) return;
for(int i = 0; i<data.texts.size();i++){
addEdit(data.texts.get(i));
}
}
or
Add this attribute to the <EditText>
tag in your custom_edit_text.xml
file:
android:saveEnabled="false"
There's nothing wrong with the code you've written to save/restore your text values. However, after onCreate()
, Android is automatically performing its own save/restore logic, and that's overwriting what you've done.
If you move your code from onCreate()
to onRestoreInstanceState()
, then your code will run after Android's automatic save/restore, so you'll "win". Or, you can disable the automatic save/restore by adding the saveEnabled=false
attribute.
The reason that Android's automatic save/restore doesn't work is that it is based on each view's android:id
attribute, and your EditText
tags all have the same id. That means that all four values get saved with the same key, so the last value overwrites all previous values.
Upvotes: 3