How Do We Get Data Binding To Use Saved Instance State?

风格不统一 提交于 2020-01-12 02:21:55

问题


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.


回答1:


I thought that ianhanniballake had a reference to a relevant answer, but maybe there is more to it. Here is my interpretation of how that reference can be applied to these circumstances.

Using the XML that you presented, the following code will alternately restore from the saved instance state and restore from the model. When the saved instance state is restored then, presumably, there is not model instantiated to restore from. That is when mCount is even. If a model exists, then the saved instance state is basically ignored and the binding takes over. There is a little more logic here than we want, but it is less than saving and restoring explicitly.

mCount is just an artifice for the sake of the argument. A flag or other indication of whether the model exists or not would be used.

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    private int mCount;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        mCount = (savedInstanceState == null) ? 0 : savedInstanceState.getInt("mCount", 0);
        if (mCount % 2 == 1) {
            // 1st, 3rd, 5th, etc. rotations. Explicitly execute the bindings and let the framework
            // restore from the saved instance state.
            binding.executePendingBindings();
        } else {
            // First creation and 2nd, 4th, etc. rotations. Set up our model and let the
            // framework restore from the saved instance state then overwrite with the bindings.
            // (Or maybe it just ignores the saved instance state and restores the bindnings.)
            Model model = new Model();
            binding.setModel(model);
        }
        mCount++;
    }

    @Override
    public void onSaveInstanceState(Bundle bundle) {
        super.onSaveInstanceState(bundle);
        bundle.putInt("mCount", mCount);
    }
}


来源:https://stackoverflow.com/questions/46650887/how-do-we-get-data-binding-to-use-saved-instance-state

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!