home.


Tagged: android-databinding


Android: Passing objects to custom views with databinding

Update: There’s now a much, much simplier version of this.

This is a bit voodoo, but that said… Let’s say you include something like this as a static method anywhere in your codebase.

@BindingAdapter("app:thing")
public static void setThing(View v, Object s) {
  Log.d("A log, innit", "Called setThing");
}

Note that the method name setThing is derived from app:thing. And the first parameter is a View and the second is an Object.

And then, in a databinding layout file, have something like

<layout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto">
  <data>
    <variable
        name="thing"
        type="String"/>
  </data>
  <com.example.blar.myapplication.CustomView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:thing="@{thing}"
      />
  ...

Then the above static method will be called when we try to set the app:thing attribute.

If in your static method you have something like this:

CustomViewDatabindingSettable st = (CustomViewDatabindingSettable) v;
st.passedDataBindingObject(s);

Then, providing your custom view implements CustomViewDatabindingSettable, you can call passedDataBindingObject passing through the databound variable.

For example, your custom view could be:

public class ListView extends FrameLayout implements CustomViewDatabindingSettable {
  ...
  @Override
  public void passedDataBindingObject(Object o) {
    Log.d("HIYA", "We're in passedDataBindingObject: " + o);
    // Now do something with the data bound object.
  }
  ...
}

Databinding and ‘instant run’ seems to mess up the autogeneration sometimes, in Android Studio 2.0 beta anyhow.

I had to uninstall the project from the device occassionally to remove dead code.

android android-databinding android-custom-views

Android: Simpler passing objects to custom views with databinding

If you have a custom view, you can pass an object to it via databinding. Let’s say you have this basic custom view.

public class CustomV extends Button {

  public static class APojo {
    private String oj;

    public String getOj() {
     return oj;
    }

    public void setOj(String oj) {
     this.oj = oj;
     }
  }

  private APojo thing;

  public CustomV(Context context) {
    super(context);
    init(null, 0);
  }

  public CustomV(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(attrs, 0);
  }

  public CustomV(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(attrs, defStyle);
  }

  private void init(AttributeSet attrs, int defStyle) {
    setText("Custom innit");
  }

  public APojo getThing() {
    return thing;
  }

  public void setThing(APojo thing) {
    this.thing = thing;
  }
}

It’s a basic custom view class, with a basic pojo class at at the top, a class variable and a getter and setter for that.

Now initialise this custom view:

<com.example.CustomV
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:thing="@{somevar.someinstance}"
/>

The somevar is just a databinding variable instance you have. someinstance is an instance of the APojo class. And the app: namespace is the standard xml:app="http://schemas.android.com/apk/res-auto".

Now when you initalise that view, the setThing method in your custom view will be called.

android android-databinding android-custom-views

Android databinding and notifying changes

We’ve gone through how to setup and do various things with databinding.

However, we’ve not specified how we can update our POJO from another thread, and have the UI thread update itself based on the new values.

(Note: Apparrently you may want to do the updates from the UI thread, especially when dealing with lists)

For this we need to tell the databinding that something has changed on each setter method. For instance:

public void setTitle(String title) {
  this.title = title;
  notifyPropertyChanged(com.newfivefour.example.BR.title);
}

The BR class here is generated by the databinding system so you can access the attribute name.

But for that BR class to generate its values you need a @Bindable annotation on the getter:

@Bindable
public String getTitle() {
  return title;
}

You must also make the class extend BaseObservable.

If you don’t want to extend you class, you can implement a particular data binding class instead.

You can even use an ObservableField generic type to avoid the whole extending or implementing thing. But, apparently, this is mainly for quick implementations, not production.

android android-databinding

Android: Loading an NavigationIcon image into Toolbar via Picasso

Let’s first include the Picasso image loading library in our app’s gradle file:

compile 'com.squareup.picasso:picasso:2.5.2'

And in our beautiful AndroidManifest.xml let us not forget the all important permissions:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Let’s now use Picasso to load our image into a mysterious Target:

Picasso.with(toolbar.getContext())
        .load("https://HELLOTHERE.com/YOURIMAGEHERE.png")
        .into(target);

What’s the target? Well, since we’re not loading directly into a ImageView, we need this object. It can’t, apparently, be inline either, lest Picasso may garbage collect it. Here it is:

Target target = new Target() {
  @Override
  public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
    Log.d("HIYA", "onBitmapLoaded");
    Bitmap b = Bitmap.createScaledBitmap(bitmap, 120, 120, false);
    BitmapDrawable icon = new BitmapDrawable(toolbar.getResources(), b);
    toolbar.setNavigationIcon(icon);
  }

  @Override
  public void onBitmapFailed(Drawable errorDrawable) {
    Log.d("HIYA", "onBitmapFailed");
  }

  @Override
  public void onPrepareLoad(Drawable placeHolderDrawable) {
    Log.d("HIYA", "onPrepareLoad");
  }
};

We get a reference to the toolbar, and set the navigation icon, after first resizing the image.

Actually, this may not be enough to ensure this isn’t garbage collection. You could make it a field. Ensure you stop the Picasso loader it when your activity/fragment/dog dies.

If you want to go wild, crazy, put it in a BindingAdapter:

@BindingAdapter("app:loadingimage")
public static void setLoadingimage(final Toolbar toolbar, String s) {
...
}

And then in your XML, with databinding, set it like this:

<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    app:loadingimage="@{appState.avatarUrl}"
    android:background="?attr/colorPrimary"
    app:popupTheme="@style/AppTheme.PopupOverlay"
    />

The fun thing about this is you may need to make the Target a static variable, else it may be garbage collected.

android android-databinding android-toolbar android-picasso

Android: Databinding and RecyclerView

If you want to do databinding in a RecyclerView:

  1. Ensure the ViewHolder has a variable for the binding.
  2. Change onCreateViewHolder to inflate your binding and pass that to the ViewHolder to return
  3. Setup the bindings in onBindViewHolder

For example:

public class MyRecycler extends RecyclerView.Adapter<MyRecycler.ViewHolder>{

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ListitemBinding viewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.listitem, parent, false);
        return new ViewHolder(viewDataBinding);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.binding.setYoyo("Position: " + position);
    }

    @Override
    public int getItemCount() {
        return 40;
    }

    public class ViewHolder extends RecyclerView.ViewHolder{
        private ListitemBinding binding;
        public ViewHolder(ListitemBinding itemView) {
            super(itemView.getRoot());
            binding = itemView;
        }
    }
}
android android-databinding android-recyclerview

Page 1 of 2
Next