Android Spinner Setting Selection with 2-Way Binding

Adrian Medioli picture Adrian Medioli · Nov 13, 2016 · Viewed 10.4k times · Source

I am struggling to get some functionality to work with Android spinners when configured with 2-way databinding. I would like to set the initial value of the spinner via the 2-way databinding on android:selectedItemPosition. The spinner entries are initialised by the ViewModel and are populated correctly, hence databinding appears to be working correctly.

The problem is with the 2-way binding of selectedItemPosition. The variable is initialised to 5 by the ViewModel but the spinner's selected item remains at 0 (the first item). When debugging it appears that the value of the ObservableInt is initially 5 (as set) but is reset to zero during the second phase of executeBindings.

Any help would be appreciated.

test_spinner_activity.xml

<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable name="viewModel"
                  type="com.aapp.viewmodel.TestSpinnerViewModel"/>
    </data>
    <LinearLayout android:layout_width="match_parent"
                  android:layout_height="wrap_content">
       <android.support.v7.widget.AppCompatSpinner
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/sTimeHourSpinner"
            android:selectedItemPosition="@={viewModel.startHourIdx}"
            android:entries="@{viewModel.startTimeHourSelections}"/>
    </LinearLayout>
</layout>

TestSpinnerViewModel.java

public class TestSpinnerViewModel {
    public final ObservableArrayList<String> startTimeHourSelections = new ObservableArrayList<>();
    public final ObservableInt startHourIdx = new ObservableInt();

    public TestSpinnerViewModel(Context context) {
        this.mContext = context;

        for (int i=0; i < 24; i++) {
            int hour = i;
            startTimeHourSelections.add(df.format(hour));
        }
        startHourIdx.set(5);
    }
}

TestSpinnerActivity.java

public class TestSpinnerActivity extends AppCompatActivity {
    private TestSpinnerActivityBinding binding;
    private TestSpinnerViewModel mTestSpinnerViewModel;

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

        binding = DataBindingUtil.bind(findViewById(R.id.test_spinner));
        mTestSpinnerViewModel = new TestSpinnerViewModel(this);
        binding.setViewModel(mTestSpinnerViewModel);
    }

I am using Android Studio 2.2.2 and Databinding is enabled.

Answer

Adrian Medioli picture Adrian Medioli · Nov 17, 2016

thank you for your suggestions. But I found the answer to my own question. It turns out that the reason that the android:selectedItemPosition=@={viewModel.startHourIdx} variable was being reset from the initialised value of 5 to 0 is because of the declaration order of the selectedItemPosition and entries attributes. In my example they were declared in that specific order and the auto-generated binding code produces initialisation in that same order.

Hence, even though the selectedItemPosition was set correctly the initialisation of the entries causes instantiation of the an ArrayAdapter which resets the selectedItemPosition to 0.

Hence, the fix is to swap the two attribute declarations in the layout file.

<data>
    <variable name="viewModel"
              type="com.aapp.viewmodel.TestSpinnerViewModel"/>
</data>
<LinearLayout android:layout_width="match_parent"
              android:layout_height="wrap_content">
   <android.support.v7.widget.AppCompatSpinner
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/sTimeHourSpinner"
        android:entries="@{viewModel.startTimeHourSelections}"
        android:selectedItemPosition="@={viewModel.startHourIdx}"/>
</LinearLayout>