TL;DR: If a layout used with data binding has an EditText
, and there is a binding expression for android:text
, the binding expression overwrites the saved instance state value... even if we do not explicitly trigger a binding evaluation. What the user typed in before the configuration change gets wiped out. How do we work around this, so that on a configuration change, the saved instance state value is used?
We have a silly Model
:
public class Model {
public String getTitle() {
return("Title");
}
}
And we have a layout that references that Model
:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="model"
type="com.commonsware.databindingstate.Model" />
</data>
<android.support.constraint.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.commonsware.databindingstate.MainActivity">
<EditText android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>
Note that this layout has no binding expressions; we'll get to that in a bit.
The layout is used in a dynamic fragment:
public class FormFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return(MainBinding.inflate(inflater, container, false).getRoot());
}
}
Note that we are not calling setModel()
anywhere to push a Model
into the binding. The MainBinding
(for the main.xml
layout shown above) is just used to inflate the layout.
This code (with a suitable FragmentActivity
to set up the FormFragment
) correctly uses the saved instance state. If the user types something into the EditText
, then rotates the screen, the newly-recreated EditText
shows the entered-in text.
Now, let's change the layout to add a binding expression for android:text
:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="model"
type="com.commonsware.databindingstate.Model" />
</data>
<android.support.constraint.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.commonsware.databindingstate.MainActivity">
<EditText android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="text"
android:text="@{model.title}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>
Now, if the user types something into the EditText
and rotates the screen, the newly-recreated EditText
is empty. The binding expression overwrites whatever the framework restored from the saved instance state.
This comes despite the fact that I am not calling setModel()
on the binding. I can certainly see where if I called setModel()
on the binding where that would replace the EditText
contents with the data from the model. But I am not doing that.
I can reproduce this behavior on both official devices (Google Pixel, Android 8.0) and ecosystem devices (Samsung Galaxy S8, Android 7.1).
This can be worked around "manually" by saving the state ourselves and restoring it at some point. For example, multiple comments have suggested two-way binding, but that runs counter to other design objectives (e.g., immutable model objects). This seems like a rather fundamental limitation of data binding, so I am hoping that there's something that I missed that I can configure to have the saved instance state be used automatically.