I am not an expert in Android but I spent hours trying to decipher the existing solutions. The good thing is that I grasped the whole idea of data binding using BindingAdapter
a bit better. For that, I am at least thankful for the existing answers (although heavily incomplete). Here a complete breakdown of the approach:
I will also use the BindingAdapter
in this example. Preparing the xml
:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="model"
type="blahblah.SomeViewModel"/>
</data>
<!-- blah blah -->
<ImageView
android:id="@+id/ImageView"
app:appIconDrawable="@{model.packageName}"/>
<!-- blah blah -->
</layout>
So here I am keeping only the important stuff:
SomeViewModel
is my ViewModel
I use for data binding. You can also use a class that extends BaseObservable
and use @Bindable
. However, the BindingAdapter
in this example, doesn't have to be in a ViewModel
or BaseObservable
class! A plain class will do! This will be illustrated later.
app:appIconDrawable="@{model.packageName}"
. Yes... this was really causing me headaches! Let's break it down:
app:appIconDrawable
: This can be anything: app:iCanBeAnything
! Really. You can also keep "android:src"
! However, take a note on your choice, we will use it later!
- "@{model.packageName}": If you worked with data binding, this is familiar. I'll show how this is used later.
Let's assume we use this simple Observable class:
public class SomeViewModel extends BaseObservable {
private String packageName; // this is what @{model.packageName}
// access via the getPackageName() !!!
// Of course this needs to be set at some
// point in your program, before it makes
// sense to use it in the BindingAdapter.
@Bindable
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
notifyPropertyChanged(BR.packageName);
}
// The "appIconDrawable" is what we defined above!
// Remember, they have to align!! As we said, we can choose whatever "app:WHATEVER".
// The BindingAdapter and the xml need to be aligned, that's it! :)
//
// The name of the function, i.e. setImageViewDrawable, can also be
// whatever we want! Doesn't matter.
@BindingAdapter({"appIconDrawable"})
public static void setImageViewDrawable(ImageView imageView, String packageName) {
imageView.setImageDrawable(Tools.getAppIconDrawable(imageView.getContext(), packageName));
}
}
As promised, you can also move the public static void setImageViewDrawable()
, to some other class, e.g. maybe you can have a class that has a collection of BindingAdapters
:
public class BindingAdapterCollection {
@BindingAdapter({"appIconDrawable"})
public static void setImageViewDrawable(ImageView imageView, String packageName) {
imageView.setImageDrawable(Tools.getAppIconDrawable(imageView.getContext(), packageName));
}
}
Another important remark is that in my Observable
class I used String packageName
to pass extra info to the setImageViewDrawable
. You can also choose for example int resourceId
, with the corresponding getters/setters, for which the adapter becomes:
public class SomeViewModel extends BaseObservable {
private String packageName; // this is what @{model.packageName}
// access via the getPackageName() !!!
private int resourceId; // if you use this, don't forget to update
// your xml with: @{model.resourceId}
@Bindable
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
notifyPropertyChanged(BR.packageName);
}
@Bindable
public int getResourceId() {
return packageName;
}
public void setResourceId(int resourceId) {
this.resourceId = resourceId;
notifyPropertyChanged(BR.resourceId);
}
// For this you use: app:appIconDrawable="@{model.packageName}" (passes String)
@BindingAdapter({"appIconDrawable"})
public static void setImageViewDrawable(ImageView imageView, String packageName) {
imageView.setImageDrawable(Tools.getAppIconDrawable(imageView.getContext(), packageName));
}
// for this you use: app:appIconResourceId="@{model.resourceId}" (passes int)
@BindingAdapter({"appIconResourceId"})
public static void setImageViewResourceId(ImageView imageView, int resource) {
imageView.setImageResource(resource);
}
}