Android
data
binding

About me

Alejandro Vidal Rodriguez
Agile Engineer @TribalScale

We are HIRING!!!!!

Agenda

  • Enable data binding in our project.

  • No more findViewById

  • Setting the model to the view

  • Binding adapters
  • Callbacks on the view

  • Observability

  • Two-way data binding

Lets build the best app ever

App with two activities and no functionality :D

  • List view with all the talks
  • Detail view each talk

Repository with steps by tags


    git clone git@github.com:antoinecampbell/android-advanced-data-binding.git

    git checkout step-1

Unleash the beast

android {
    ....
    dataBinding {
        enabled = true
    }
}

Well done!

Get rid of findViewById


private void initViews() {
    talkImage = (ImageView) findViewById(R.id.talk_detail_image);
    talkTitle = (TextView) findViewById(R.id.talk_detail_title);
    talkRating = (TextView) findViewById(R.id.talk_detail_rating);
}

ActivityTalkListBinding binding = 
                DataBindingUtil.setContentView(this, R.layout.activity_talk_list);

with

Data binding takes care


@Override
public void onDataAvailable(List<Talk> data) {
    binding.listOfTalks.setLayoutManager(new LinearLayoutManager(this));
    binding.listOfTalks.setAdapter(new TalkAdapter(data, this, this));
}

a class with the XML name in camel case will be created, in our case  ActivityTalksListsBinding


public ActivityTalkListBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
    super(bindingComponent, root, 0);
    final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds);
    this.headerImage = (android.widget.ImageView) bindings[1];
    this.listOfTalks = (android.support.v7.widget.RecyclerView) bindings[2];
    this.mboundView0 = (android.widget.LinearLayout) bindings[0];
    this.mboundView0.setTag(null);
    setRootTag(root);
    // listeners
    invalidateAll();
}

prepare your view xml

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="model"
            type="gof.com.databindingtalk.models.Talk"/>

    </data>

...

</layout>
 <TextView
    android:id="@+id/talk_title"
    style="@style/talk_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{model.title}"
    tools:text="Talk title"/>

how to fill that <data> object

View contactView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.talk_item, parent, false);
@Override
public void onBindViewHolder(TalkViewHolder holder, int position) {
    Talk currentTalk = talks.get(position);
    holder.getBinding().setModel(currentTalk);
    Picasso.with(context).load(currentTalk.imageUrl)
                         .into(holder.getBinding().talkItemImage);
    holder.getBinding().executePendingBindings(); // important!
}
 DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), 
                                R.layout.talk_item, parent, false);

from

to

data

Awesome!

BindingAdapters

or how to create your own XML tags

<ImageView
    android:id="@+id/talk_item_image"
    android:layout_width="120dp"
    android:layout_height="120dp"
    android:background="@color/light_gray"
    app:loadImageFromUrl="@{model.imageUrl}"/>

The powerful and dangerous

@BINDINGADAPTER

public class TalkBindingAdapters {
    
    @BindingAdapter("loadImageFromUrl")
    public static void loadImageFromUrl(ImageView imageView, String url) {
        if (imageView != null) {
            Picasso.with(imageView.getContext()).load(url).into(imageView);
        }
    }

    @BindingAdapter({"placeHolder", "loadImageFromUrl"})
    public static void loadImageFromUrlWithPlaceHolder(ImageView imageView, 
                                        Drawable placeHolder, String url) {
        if (imageView != null) {
            Picasso.with(imageView.getContext()).load(url)
                                 .placeholder(placeHolder).into(imageView);
        }
    }
}

binding adapters don't need to be static and can be injected.

Actions & lambdas on XML

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

    <data>

        <variable
            name="model"
            type="gof.com.databindingtalk.models.Talk"/>

        <variable
            name="navigation"
            type="gof.com.databindingtalk.base.AppNavigation"/>

    </data>

    <android.support.v7.widget.CardView
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:clickable="true"
        android:foreground="?android:attr/selectableItemBackground"
        android:onClick="@{() -> navigation.navigateToTalkDetail(context, model.id)}">

careful there...

Observability

public class Talk {
    public String title;
    public int rating;
    public String imageUrl;
    public String id;

    public Talk(String title, int rating, String imageUrl, String id) {
        this.title = title;
        this.rating = rating;
        this.imageUrl = imageUrl;
        this.id = id;
    }
}
public class Talk {
    public ObservableInt rating = new ObservableInt();
    ...

    public Talk(String title, int rating, String imageUrl, String id) {
        ...
        this.rating.set(rating);
    }
}

from

to

lets fake an update

public class TalkListPresenter extends BasePresenter {
    public TalkListPresenter() {
    }

    public void getTalks() {
        final List<Talk> talks = new ArrayList<>();
        talks.add(new Talk("Databinding with android", 3, "...", "1"));
        ...
        view.onDataAvailable(talks);
        delayUpdate(talks);
    }

    private void delayUpdate(final List<Talk> talks) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                talks.get(0).rating.set(1000);
            }
        }, 2000);
    }
}

Auto updates the UI

Animate with @bindingAdapter

@BindingAdapter("app:blinkOnChange")
public static void blinkOnChange(TextView textView, int oldContent, int newContent) {
    if (oldContent != 0 && textView != null && oldContent != newContent) {
        int colorFrom = ContextCompat.getColor(textView.getContext(), R.color.transparent);
        int colorTo = ContextCompat.getColor(textView.getContext(), R.color.colorAccent);
        int textColorFrom = ContextCompat.getColor(textView.getContext(), R.color.black);
        int textColorTo = ContextCompat.getColor(textView.getContext(), R.color.white);

        AnimatorSet animations = new AnimatorSet();
        AnimatorSet inBlink = new AnimatorSet();
        inBlink.playTogether(UIUtils.getValueBackgroundAnimator(textView, colorFrom, colorTo),
                UIUtils.getValueTextColorAnimator(textView, textColorFrom, textColorTo));

        AnimatorSet outBlink = new AnimatorSet();
        outBlink.playTogether(UIUtils.getValueBackgroundAnimator(textView, colorTo, colorFrom),
                UIUtils.getValueTextColorAnimator(textView, textColorTo, textColorFrom));
        animations.playSequentially(inBlink, outBlink);

        animations.start();
    }
}

any binding adapter can receive old and new values

Two way databinding

<TextView
    android:id="@+id/talk_title"
    style="@style/talk_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{model.title}"
    tools:text="Talk title"/>

if we can get model updates with:

<EditText
    android:id="@+id/talk_title"
    style="@style/talk_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={model.title}"
    tools:text="Talk title"/>

we can update our model with:

Beware of responsibility

Never put biz logic in view


android:onClick="@{() -> service.purchaseItem(model.itemId)}">

Your code must be readable


android:visibility="@{model.age > 18 ? 
        Collection.split(model.name).get(0,8) : 
        String.format(model.surname + model.country)}"
        

android:visibility="@{model.publicName}"
        

Too powerful @bindingAdapters

@BindingAdapter({AUTO_SCROLL_LINEAR_LAYOUT, ELAPSED_TIME})
public static void autoScrollRecyclerView(final RecyclerView view, final boolean autoScroll, final long elapsed) {
if (!autoScroll) return;

view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
    TimerTask task;

    @Override
    public void onViewAttachedToWindow(View v) {
        Timer autoScrollTimer = new Timer();
        RecyclerView recyclerView = (RecyclerView) v;
        recyclerView.smoothScrollToPosition(0);

        task = new TimerTask() {
            @Override
            public void run() {

                new UiJob(() -> {
                    if (recyclerView != null) {
                        final LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
                        if (manager != null) {
                            int current = manager.findFirstVisibleItemPosition();
                            int count = manager.getItemCount();
                            recyclerView.smoothScrollToPosition(current == count - 1 ? 0 : current + 1);
                        }
                    }
                });
            }
        };

        autoScrollTimer.scheduleAtFixedRate(task, elapsed, elapsed);
    }

    @Override
    public void onViewDetachedFromWindow(View v) {
        task.cancel();
    }
});
}

Questions?

contact

goofyahead@gmail.com

https://www.linkedin.com/in/alejandrovidalrodriguez

https://medium.com/@goofyahead​

https://github.com/goofyahead

Android data binding

By Alejandro Vidal Rodriguez

Android data binding

  • 25