Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android BindingAdapter order of execution?

I need to understand how the Data Binding Library determine the order of execution for its BindingAdapters. If I have two BindingAdapters for a View and if the View has both the attributes corresponding to those BindingAdapters, how will it determine which adapter will be executed first? I ask because the order of execution matters in my case.

I have the following BindingAdapter/s:

public class SpinnerBindingAdapter {

    @BindingAdapter(value = {"entries"})
    public static void setEntries(Spinner spinner, List<? extends SpinnerItem> spinnerItems) {

        for (int i = 0; i < spinnerItems.size(); i++) {
            spinnerItems.get(i).setIndex(i);
        }
        ArrayAdapter<? extends SpinnerItem> adapter =
                new ArrayAdapter<>(spinner.getContext(), R.layout.spinner_item, spinnerItems);
        spinner.setAdapter(adapter);
    }

    @InverseBindingAdapter(attribute = "selectedItem", event = "selectedItemAttrChanged")
    public static Object getSelectedItem(Spinner spinner) {

        Object selectedItem = spinner.getSelectedItem();

        return selectedItem;
    }

    @BindingAdapter(value = {"selectedItem"})
    public static void setSelectedItem(Spinner spinner, SpinnerItem spinnerItem) {
        if (spinner.getAdapter() == null) {
            return;
        }
        // Other code omitted for simplicity
    }

    @BindingAdapter(value = {"selectedItemAttrChanged"}, requireAll = false)
    public static void setOnItemSelectedListener(Spinner spinner, final InverseBindingListener selectedItemChange) {
        if (selectedItemChange == null) {
            spinner.setOnItemSelectedListener(null);
        } else {
            spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

                @Override
                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                    selectedItemChange.onChange();
                }

                @Override
                public void onNothingSelected(AdapterView<?> parent) {

                }
            });
        }
    }
}

And here is how I populate the Spinner and set the selection:

<Spinner
    android:id="@+id/spinner_system_activity_edit_tracker_unit"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="fill_horizontal"
    app:entries="@{DatabaseModel.queryForAll()}"
    app:selectedItem="@={object.selectedItem}"/>

DatabaseModel.queryForAll is a static method that queries the database and returns a list of objects which is then given to the BindingAdapter. The BindingAdapter takes this list, update each of its item with an index and set it as an adapter for the spinner.

For whatever reason, the "setSelectedItem" BindingAdapter always gets called first. This is undesirable, because I need the entries to be initialised first. If its not initialised first then spinner.getAdapter() will be null when setSelectedItem is first called. Which means that previous saved selection will not be restored.

like image 202
chaser Avatar asked Dec 02 '16 13:12

chaser


People also ask

How do I use BindingAdapter on Android?

To create a custom binding adapter, you need to create an extension function of the view that will use the adapter. Then, you add the @BindingAdapter annotation. You have to indicate the name of the view attribute that will execute this adapter as a parameter in the annotation.

What is @BindingAdapter?

implements Annotation. android.databinding.BindingAdapter. BindingAdapter is applied to methods that are used to manipulate how values with expressions are set to views.

What is BindingAdapter Kotlin?

A binding adapter is simply a static or instance method that is used to manipulate how some user defined attributes map data bound variables to views. Again, to elaborate on an example from the documentation, say we want to set the left padding on a view.


Video Answer


1 Answers

There is no guaranteed order of execution in Android Data Binding. Because of this, you should merge binding adapters that have reliance on multiple attributes. In your case, you need to merge the binding adapter for selectedItem and entries.

@BindingAdapter(value = {"selectedItem", "entries"}, requireAll = false)
public static void setSelectedItem(Spinner spinner, SpinnerItem spinnerItem,
        List<? extends SpinnerItem> spinnerItems) {
    // Set entries attribute when provided
    if (spinnerItems != null) {
        for (int i = 0; i < spinnerItems.size(); i++) {
            spinnerItems.get(i).setIndex(i);
        }
        ArrayAdapter<? extends SpinnerItem> adapter =
            new ArrayAdapter<>(spinner.getContext(), R.layout.spinner_item, spinnerItems);
        spinner.setAdapter(adapter);
    }
    // set selectedItem attribute when provided
    if (spinnerItem != null) {
        if (spinner.getAdapter() == null) {
            return;
        }
        // Other code omitted for simplicity
    }
}
like image 144
George Mount Avatar answered Sep 18 '22 05:09

George Mount