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 OnPropertyChangedCallback
s. 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.
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.
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.
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.
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 ).
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With