Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating UI using data binding library

Context:

I'm using v1.0-rc1 of the new data binding library.

I have the following view model:

public class DrawerPageHeaderViewModelImpl extends BaseObservable implements DrawerPageHeaderViewModel {

    @Nullable
    private Location currentLocation;

    public DrawerPageHeaderViewModelImpl(@Nullable final Location currentLocation) {
        this.currentLocation = currentLocation;
    }

    @Bindable
    @Nullable
    @Override
    public String getDistanceDisplayString() {
        if (currentLocation == null) {
            return null;
        }

        float[] results = new float[1];
        Location.distanceBetween(landmark.getLatitude(), landmark.getLongitude(), currentLocation.getLatitude(), currentLocation.getLongitude(), results);
        final float metersToTargetLocation = results[0];

        final float feetToTargetLocation = DistanceUtil.convertMetersToFeet(metersToTargetLocation);
        return DistanceUtil.convertFeetToFeetOrMilesString(feetToTargetLocation);
    }

    @Override
    public void setCurrentLocation(@Nullable final Location currentLocation) {
        this.currentLocation = currentLocation;
        notifyPropertyChanged(BR.distanceDisplayString);
    }

}

This view model is passed to a Fragment and stored in an instance variable. The view model is then bound to a layout in the Fragment's onCreateView callback (here headerView is an empty FrameLayout):

    @Nullable
    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
        final View v = inflater.inflate(R.layout.fragment_drawer_page, container, false);

        headerView = (ViewGroup) v.findViewById(R.id.headerView);
        final ViewDrawerPageHeaderBinding binding = DataBindingUtil.inflate(inflater, R.layout.view_drawer_page_header, headerView, true);
        binding.setViewModel(viewModel);

        return v;
    }

Periodically, viewModel.setCurrentLocation is called and passed the user's current location:

@Override
public void update(final Observable observable, Object data) {
    new Handler(Looper.getMainLooper()).post(() -> {
        if (isAdded()) {
            viewModel.setCurrentLocation(locationController.getCachedUserLocation());
        }
    });
}

Current behavior:

The UI correctly displays the distance String when each Fragment is first created. The UI correctly displays the distance String each time a Fragment is recreated (these fragments live in a ViewPager.

The UI does NOT update when viewModel.setCurrentLocation is called with a new location.

Desired behavior:

The UI updates each time viewModel.setCurrentLocation is called with a new location.

Stuff I've looked at/thought about so far:

As far as I can tell, having the view model implement Observable (in this case, via extending BaseObservable) is supposed to automatically make the UI update when notifyPropertyChanged is called. At least, when I look at the Android documentation for data binding, that's the message I take away.

The BaseObservable class maintains a private list of OnPropertyChangedCallbacks. If I set a debug breakpoint on the BaseObservable.notifyPropertyChanged method:

public void notifyPropertyChanged(int fieldId) {
    if(this.mCallbacks != null) {
        this.mCallbacks.notifyCallbacks(this, fieldId, (Object)null);
    }
}

I see that mCallbacks is always null at runtime. So presumably, the generated data binding stuff does not call BaseObservable.addOnPropertyChangedCallback to provide an OnPropertyChangedCallback that automatically connects components. Does that mean I need to do it manually? That would seem to defeat a lot of the point of the data binding library.

like image 701
stkent Avatar asked Sep 18 '15 17:09

stkent


People also ask

What is UI data binding?

UI data binding binds UI elements to an application domain model. Most frameworks employ the Observer pattern as the underlying binding mechanism. To work efficiently, UI data binding has to address input validation and data type mapping.

What is the data binding library (DB Library)?

The Data Binding Library (referred to as the ‘DB library’ for the rest of this post) offers a flexible and powerful way to bind data to your UIs, but to use an old cliché: ‘with great power comes great responsibility’. Just because you’re using data binding does not mean that you can avoid being a good UI citizen.

What is data binding library in Salesforce?

The Data Binding Library is a support library that allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically. Layouts are often defined in activities with code that calls UI framework methods.

How do you bind data in a framework?

Most frameworks employ the Observer pattern as the underlying binding mechanism. To work efficiently, UI data binding has to address input validation and data type mapping. A bound control is a widget whose value is tied or bound to a field in a recordset (e.g., a column in a row of a table ).


1 Answers

Hello this worked on mine. I had to post it as answer due to lack of enough credit.

public class DataBindingTest extends Fragment {


private LinearLayout headerView;

public DataBindingTest() {
    // Required empty public constructor
}

private static Handler mHandler;
DrawerPageHeaderViewModelImpl viewModel;

private Runnable runnable;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View rootView = inflater.inflate(R.layout.fragment_font_test, container, false);
    headerView = (LinearLayout) rootView.findViewById(R.id.headerView);
    viewModel = new DrawerPageHeaderViewModelImpl(null);
    mHandler = new Handler();
    RecyclerItemBinding bindingView = DataBindingUtil.inflate(inflater, R.layout.recycler_item, headerView, true);
    runnable = new Runnable() {
        @Override
        public void run() {
            changeLocation();
        }
    };
    bindingView.setViewModel(viewModel);
    return rootView;
}

@Override
public void onResume() {
    super.onResume();
    changeLocation();
}

@Override
public void onPause() {
    super.onPause();
    mHandler.removeCallbacks(runnable);
}

public void changeLocation() {
    viewModel.setCurrentLocation(new Location("New"));
    mHandler.postDelayed(runnable, 2000);
}

public class DrawerPageHeaderViewModelImpl extends BaseObservable {

    @Nullable
    private Location currentLocation;

    public DrawerPageHeaderViewModelImpl(@Nullable final Location currentLocation) {
        this.currentLocation = currentLocation;
    }

    @Bindable
    @Nullable
    public String getDistanceDisplayString() {
        if (currentLocation == null) {
            return null;
        }

        return "Some String " + new Random().nextInt(100);
    }

    public void setCurrentLocation(@Nullable final Location currentLocation) {
        this.currentLocation = currentLocation;
        notifyPropertyChanged(BR.distanceDisplayString);
    }

}

}

And layout file:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/tools">

<data>
    <variable
        name="viewModel"
        type="com.androidbolts.databindingsample.DataBindingTest.DrawerPageHeaderViewModelImpl" />
</data>

<android.support.v7.widget.CardView
    android:id="@+id/card_view"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="100dp"
    android:layout_height="130dp"
    android:layout_gravity="center"
    card_view:cardCornerRadius="2dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="100dp"/>

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{viewModel.distanceDisplayString}"
            app:font="@{@string/kenyan}"/>

    </LinearLayout>
</android.support.v7.widget.CardView>
</layout>
like image 65
subhash Avatar answered Nov 01 '22 20:11

subhash