Practice English Speaking&Listening with: Data Binding -- Write Apps Faster (Android Dev Summit 2015)

Difficulty: 0


GEORGE MOUNT: Hello everyone, I'm

George Mount from the UI Toolkit team,

and I'm here to talk to you about Android Data Binding.

Before we started this I sought some random Android

developer out there for some help in this talk

and I'd like to introduce you to-- I'm

sorry what was your name?

YGIT BOYAR: Random123.

GEORGE MOUNT: Random123, why don't you come up here?


I stole-- I hope you don't mind, I have some friends in the NSA,

I looked at your laptop and put it up here on the screen.

You guys, don't tell anyone.

YGIT BOYAR: My code.

So, I love Android.

I love that little green robot.

But as a developer, some parts I feel are terrible.

I'm writing so much code, George.

It's like, why is this like this?

Why all this stupid code here?

Why do I need that?

Like this product image: Set that, set this, set this.

Did I study computer science for this?

I don't think so.


We've got to have something better than that, right?

YGIT BOYAR: We have something in the box.


Let's talk about Android Data Binding, OK?

All right, let's look at your code in a little bit of detail.

I see this set product UI you are

using calling from onCreate.

That's just a pain, let's get rid of it.

We don't want that.

Instead, let's look at this onCreate.

Let's, instead, just bind your layout to this binding code

and then set the product on there

and let the binding take care of all the work.

Sound good?

A little better?

YGIT BOYAR: Let's see about that.

I'm skeptical.

GEORGE MOUNT: Let's look at your layout.

Wow, you have a lot of ellipses in your layout, man.

What's up with that?

To make sure that we understand that it's a data binding

layout, first we add this layout tag to the outside,

and then we get this variable, the prod variable,

that we set in onCreate.

And then we can use that prod variable

anywhere in our layout.

So instead of this ID we can now access that prod variable.

So, for example, in the source it's an image problem

so we can access it directly.

The product name, it's a string.

And same with the description.

YGIT BOYAR: There is a bug, though.

There you go.

You set a price, it's a crash.


All right, well our price is-- if you look at the code there

it's a resource, right?

And our resource is a string and it's doing a format.

We should do better in our expression, right?

YGIT BOYAR: I expect so.

GEORGE MOUNT: I think so.

One good thing is that now we can access our layout--

our expression stuff directly in our layout.

So we can have this access to the string resource

right there, and pass a parameter.

We're passing the product price as a parameter in our resource,

in our layout.

Pretty cool, huh?

YGIT BOYAR: But in my application things

are not always starting, George.

I'm cool with your demo.

But I have this application, sometimes

I have a different layout.

Yes, there you go.

It might be on sale.

How do I do this?

I need to write more code to do this.

GEORGE MOUNT: Whine, whine, whine, whine.

You want everything, don't you?

We allow you, inside your layout,

to have some little dynamism in there.

You can have an expression that says, if you're on sale

then show this on sale sign, and if you're on sale

you might also have a different color for your price.

YGIT BOYAR: Your example is not very realistic because my price

changes, we're a dynamic website.

So I do dynamic pricing.

How you handle that?

GEORGE MOUNT: Well, you have to at least tell us

when it changes.

So let's look at your product.

Your product has a price and name and image.

You have to just extend the base observable class,

and then we mark the things that you

can change that are going to change as bindable.

And then you have to tell us when it changes.

So we have this notify property change and the bindable part--

YGIT BOYAR: But my classes extend the base class.

I can't extend your class, that is not possible.


All right, all right, fine.

We can use an observable interface instead.

Is that better?

YGIT BOYAR: Maybe, let's see about that.

GEORGE MOUNT: We'll give you a little bit of help here.

We'll give you this property change registry

so you can just tell us when that happens.

YGIT BOYAR: Yeah, that's good enough.

GEORGE MOUNT: That's a little better.

This is a little easier.

It would be nice if we could just have properties

that are themselves observable.

Wouldn't it be?

Here you can access them as if it was just the same

with the same expression.

You just say your prod.image, and even

though it's not a drawable itself we

pretend that it's a drawable.

It's an observable field and it's observable double.

Double for price?

YGIT BOYAR: Did you just double the price?


GEORGE MOUNT: You shouldn't be using doubles for your prices.

Or, if you have more blobby data,

like let's imagine coming from JSON

and you're still doing development,

everything's up in the air, you don't

know what it's going to be like you can use a map.

And that's pretty nice for prototyping.

YGIT BOYAR: Seems interesting.

How about multiple layouts?

GEORGE MOUNT: Multiple layouts?

I guess you have two different variables

and different layouts.

YGIT BOYAR: So I like two activities now?

GEORGE MOUNT: I hope not.

Boy, that would be a pain, wouldn't it?

In one you're setting a product and the other one

you're setting the product list, it's really a pain.

You don't want to have to do if it's

on the left-- landscape do this, and if your portrait do this.

So instead what we do is we merge all of your variables

and we produce one that has the summation of all the variables

that you need, and the binding will just say,

"Oh, I only need these what variables,

and I'll use those in my layout.

The rest of them I'll just not use at all."

You just set it and forget it, it's great.

Just like Ronco, right?

YGIT BOYAR: That seems good.

Talk about the down sides.

GEORGE MOUNT: But wait, there's more.

You don't need to do any more findViewByID() right?

Because now all your expressions are right there

in your layout file.

YGIT BOYAR: Yeah but I run animations

and also put them into the data binding.

It seems weird.


You still don't need findViewByID().

If you have a layout a view with a tag ID in it, then what we do

is we automatically extract that view

and stick it as a public static-- final field

in your binding.

So you can access it directly from binding.

YGIT BOYAR: That sounds good.

GEORGE MOUNT: You really don't need findViewByID() anymore.

YGIT BOYAR: I don't write that code.

GEORGE MOUNT: That stuff is a pain, isn't it?

Also, we have these automatic properties.

For example, here is this DrawerLayout,

and there's this is really nice setter-- setScrimColor.

Wouldn't it be nice to be able to just tag it

so you could just set it right there in your layout file?

There's no attribute for that.

Well, we can just kind of sneak in and say, "Oh, setScrimColor.

That looks a lot like a scrim color, right?

Just to set it right in front of it."

So we just look at it say, "setScrimColor."

Automatically we just say, OK if you

have this thing with a data binding expression in it,

then we will look for the set center with it, that takes

of the same type of parameter.

So in this case the color scrim is an int,

so we say setScrimColor.

We know exactly what it is.

It's just automatic for you, you don't

have to create this new attribute for yourself.

And everyone's favorite on click.

Pretty awesome.

YGIT BOYAR: Infamous.

GEORGE MOUNT: So we have this as well,

we can do onClick with expressions.

Pretty nice.

YGIT BOYAR: How does it work?

GEORGE MOUNT: I think we'll talk about that in a little bit.

We have all the other event handlers too,

like onLongClick or onItems.

Whatever, all those things.

onTextChanged is a real interesting one,

because it's a text watcher and there

are three handlers on there.

You can just set one of them, two of them, three of them.

Whatever you want, it's pretty nice.

How much would you pay?

How much [INAUDIBLE] performance you would pay for this?


YGIT BOYAR: No, of course not.


YGIT BOYAR: Uh, maybe.

GEORGE MOUNT: How about nothing?

YGIT BOYAR: Wow, no way.

He's lying.

GEORGE MOUNT: It might not be nothing,

depending on how optimal your code is, but it's pretty good.

I want to bring up my co-conspirator in this.

Let me introduce you to Ygit Boyar.


YGIT BOYAR: Hey, everybody.

I'm Ygit from the UI Toolkit Team,

I work on data binding with George.

I'm going to go into some details on how it actually


As George showed before the layouts

are a little bit different now.

You have a layout tag in the root.

This tells us it's data binding layout.

And we pass it into the layout grinder,

which processes your layouts, and then explores a new layout

that the older Android versions can understand,

which doesn't add a layout tag anymore,

remove the namespaces to your first child.

And it also has some sub-products

from the processing, it creates a class,

it creates all these public final fields for your views

with IDs, and it creates the setters for your variables.

Going into detail, this is what actually is happening.

We move that namespace to her first child,

get rid of the layout tag.

And this is done while your application

is compiled by Gradle.

And then we remove your expressions add some tag there

so we can find those [INAUDIBLE] afterwards.

If you had an ID we would keep it there, and also

create the vies.

As you can see there directly matching this public file.

You don't need to cast anymore because we know what type it

is at compile time, so we just create the variable

with that type.

So, how do we do this?

One important aspect of data binding

is that almost everything is done at compile time.

So when we see an expression like this we--

while your Java code is being compiled,

we go check, "OK, this product, we

know what type it is because you told us."

Now we say, "OK product is an image field or an image


We will look for these things.

OK, here we found there is a drawable returning

getImage method.

Then we check, OK, I have a drawable,

there is a source attributes.

Can I set this drawable to the source attribute?

And then we call set source there.

The cool part is, we don't need any reflection

to do any of these things because we do it

at compile time.

So at runtime it is equal to the code

that you would write yourself.

GEORGE MOUNT: Hey wait a second, I thought

all databyte [INAUDIBLE] frameworks needed reflection,

don't they?

YGIT BOYAR: Not the new ones.

GEORGE MOUNT: Not this one?

Oh, nice.

YGIT BOYAR: So for example-- but is not

true that the examples are simple.

So you can say I have my variable, said to this text.

Correct, that's straightforward.

It sets the user of that image.

The source.

I call setSrc which I said before, which does not exist.

So this is a problem because the ImageView

doesn't have a setSrc method.

It has a source attribute, but as a setImageDrawable method,

[INAUDIBLE] you can pass that drawable.

How does data binding know about this?

It's done by annotation.

So we had this binding method annotation which-- you can say,

OK, this is a binding annotation for a type class ImageView

that attribute a source and the method is

called setImageDrawable.

So we know that at the resource maps are setImageDrawable,

if the pass parameters match we can use that method

to run the binding.

Again, this is done in the compile time.

What we generate is the same code

that you will do at setImageDrawable.

[INAUDIBLE] event handler.

These are a little tricky because unfortunately Java 7

didn't have any function parameters, you cannot do them.

So the way this works is, we know

there's a binding method that says onClick.

Method setOnClickListner.

OK, what does the setOnClickListener expect?

It expects a [INAUDIBLE] .onClickListener

that's an interface or [INAUDIBLE] and then

the interface has one method.

Now if you look for that handler.clicked

matches that one method.

Going into the details, we saw this.

Nice, that the handler.clicked method receives a view

and this matches so we create the interface for you

so that Java is happy, and you're going to tunnel

those calls to our handler.

So you can see everything is matching.

It just works.

GEORGE MOUNT: You're stealing my thunder here.

Let's talk about binding adapters.

Binding adapters are probably the coolest part

about data binding.

So let's talk about something a little bit

more complicated than setText.

SetText was a nice automatic variable.

You can just setText, it takes in a string--

or, actually to setText takes in an char sequence

but it's pretty close.

Let's talk about something more interesting.

We have this ImageView but we have a large URL,

large image data coming down.

And if we do this, what's going to happen--

we don't want this on the UI thread.

It's going to load everything on the UI thread.

That that's pretty lousy, I don't like that.

So instead, I'm going to use a tool.

How about Picasso?


What's your favorite?

YGIT BOYAR: I'd rather not say that.

GEORGE MOUNT: In this example whoever

it was who wrote this expression tried to use Picasso,

but my goodness, Picasso with context-- that's

a heck of an expression, and into what do you--

I don't know.

That's lousy.

I don't like this.

We don't want to see that in our expressions anyway.

So let's do something else simpler.

Instead, what we're going to do is create a binding adapter

and we're going to use the Android source.

We're going to override what the default behavior is

with our binding adapter.

And instead of doing the normal stuff

we're going to try to do something special.

So what we're going to do is set this binding adapter,

and of course this is an attribute, an annotation

on our static method here.

And it could be any static method anywhere.

And we're just going to look for that static method

with the attribute, the annotation.

And here we're going to look for something that is an ImageView

and it takes in a string as a parameter.

And now what we can do is do anything we want in that class.

So we have this nice Picasso.

We can use their context from the view.

We can load the image in there, and it

can be done all off the UI thread,

and it's all kind of magic.

Now you can do image source is equal to some value,

it's great.

But what if you want to do something more interesting.

Let's imagine you want to have a placeholder

for your annotation.

Now you have to do this with Picasso in the same call,

you can't just say, "in one call it's

called the load in the other one you do--"

so what we're going to do is we're

going to merge it all together into one binding adapter.

This one takes two kinds of attributes,

and they can work together.

You just take the parameters in the order

that you've assigned them in the annotation,

and now you can do whatever you want in the method.

In this case we're going to load the URL off-thread,

and in the meantime we'll load this placeholder image.


Now sometimes you want to have some kind of instance

information in your adapter.

And before we were using static methods.

But what happens if you want to use an instance?

With the static method it's easy.

We know what to do.

We can call it directly just my class static method call.

Very simple to do in the generated binding class

that we have.

YGIT BOYAR: How do you get the instance?

GEORGE MOUNT: So we take out the static what happens?

Where does that instance come from?

Some kind of instance.

So what we're going to do is-- the binding framework

is going to look and see, "Oh, this is an instance binding


We need to generate this method on this class, this data

binding component class."

And this method is going to be called

get whatever the name of your adapter class is.

And now it's your job to implement

to implement this class.

So you've been with this class, and then you

call setDefaultComponent on it.

Now, there's also ones if you want

to load a specific implementation

for a specific binding if you want.

But most of the time you probably

want just the default component that you're going to load.

Then we know exactly what instance

we're going to load for your binding adapter.

Very nice.

Now you can also do this with components.

Now when you're doing it with a component

if you have some kind of state you want to get.

Imagine this is a cache for your images, right?

So you want to load the image from your cache first, and then

possibly load off-thread.

In this case we need to go what that instance is.

Where we're going to pass it as a static method,

but we want to have some kind of instance

that we're going to share around to all

the different binding adapters.

So in this case we need to know what that state is.

So in this case you implement your data binding component

but you add your own method to it.


So when you had your own method to it,

we don't know anything about this method,

this is something that you know about.

But we're going to pass it to you as the first parameter,

if you'd like.

Now, you can do whatever you want.

So we have our own cache here that we can load our data from.

Let's talk a little bit about best practices.

YGIT BOYAR: Let's go through some examples.

GEORGE MOUNT: They say practice makes perfect.

YGIT BOYAR: Practice.

GEORGE MOUNT: Practice, man.

YGIT BOYAR: [LAUGHS] Internal joke, sorry.

Let's look at some examples.

Data binding allows you to write some explorations in the XML.

That means I can just send a web service call from my XML,


Why not?

There's a click listener.

There's an API.

Why should I not do this?

You should not do this.

If you do this, your application will break.

This is not what data binding is for.

Data binding and is to put data into your UI from your model.

So instead of doing that, have a call

back, have a presenter, whatever model you prefer.

Just [INAUDIBLE] the call to the Java

when you do the real thing.

Don't try to do business logic tree here or something.

Its not going to work.

Another example in a good way is that, I

want to change this drawable based on the users [INAUDIBLE]

just reacted.

In that case it's fine.

Just what the eye sees is a super simple expression.

That's OK, because here's an advantage, when I look at this

here it's OK, I know that if the age is this UI

shows this drawable.

Otherwise the other one is very clear,

it makes my example better, my code cleaner.

So this is OK.

So you have to decide it for yourself.

Well, if you do something like this, [INAUDIBLE] redact it.

I don't understand what this code is doing when I look

at it, then there's a problem.

It doesn't make sense.

Instead of doing it, there's something simpler.

If the age is this, the displayName otherwise redacted.

And the displayName comes from your model.

If your object doesn't really have that field, which

it doesn't in this case, you create a view model

that provides that information, or you

can make the variable for your binding layout.

GEORGE MOUNT: I think you took that from my code, I'm sorry.

YGIT BOYAR: It's not very good.

He got data binding wrong.


YGIT BOYAR: So if your real model

can display [INAUDIBLE] in this example

we make this displayName bindable.

In return, data binding is going to generate this BR clause

similar to the R clause, but for bindable things.

And when the user's last name is set you can [INAUDIBLE]

that it has changed and data binding

will keep the UI up to date.

If you have value objects and you're directly showing them

in your UI, that's okay to set them

as variables in your layout, and set [INAUDIBLE]

your generated binding class.

But if this is not the case, if you need more information

consider using a view model where you put this information

there, and you can just use these observable convenience

fields we provide your view models.

You just set them and data binding

makes sure of that they are visible on the UI.

By the way, observables like those convenience

observable classes we provide, they're

nice if there are very few.

If you tried to make your value objects with those things

you're just using a lot of my memory for no reason.

Don't do that.

But they're nice if you have a view model

or you could have them in your activity

if there's something you want to easily control.

So there's other examples where you

write the view model extending the base observable

class because you don't want to use those convenient classes.

The same example here, when you the data changes you notify,

and then data binding takes care of this.

Or you can implement observable interface,

now we provide this program to change

registry class which allows you to dispatch your [INAUDIBLE].

All they have to do is, just notify PropertyChange

through that class.

And when we add and remove callbacks,

just send those calls to this class

and it will take care of it.

The other part is-- This is a beautiful example.

I know many people have been asking for this font editor

within TextView.

There's some reasons for that.

You ask the [INAUDIBLE].

Lisa, I don't know if she's here,

she's created this thing where I can set my fonts in XML.

I want to do that, it is much cleaner.

This became very popular, I like this example.

The good thing about this example

is it makes your XML very clean.

You look at it, you understand what it is trying to do.

There's no added performance cost in all of these things.

So you write the binding adapter that says,

I know how to set a font on a textView.

This is all well defined.

In this code you get the font.

You load the typeface.

Set it on the text view.

If this was a real application you

would probably cache it so you don't keep reloading the font.


Another good example is the image adapter.

I'm using any image loader here, Glide.

I could say, this adapter knows how to set a photo URL

and might have a default but they're not required.

So that if there is only a photo URL set on the view

we will still call the adapter the other value you will see

will be the default value, which is if it is an object

it will be null.

So an integer will be zero and all the default

values you can imagine.

This way I can write just one adapter

that can handle multiple attribute groups.

And if it is an instance method you

can just create this AppComponent

that implants the DataBindingComponent

and just returns your image adapter.

This is very nice for testing so that while the test is running

you can basically set the default, or do nothing,

because no one wants to call network during a test

unless you are testing the network.

So I have this mock version here that I inject during my test.

And it just does nothing.

It uses the default drawable.

So this works one-on-one with [INAUDIBLE].

You can just say inject ImageAdapter, create this app

component and for example, if you are using Dagger 2

this will take care of the rest.

You don't need to the anything else

and this is a Dagger 2 implementation where we always

say-- We create the model for the stuff,

and we implement this, and then Dagger

generates the rest of the code assuming that you provided it.

And thank you.


GEORGE MOUNT: We were super fast so we have

time for lots of questions!

YGIT BOYAR: There are two microphones left and right.

Please use them to ask questions.

AUDIENCE: Hi, my question is about when you use data binding

it's not always obvious what's happening in the UX editor.

If you understand what I mean.

It would be nice to be able to provide a default

product, or something to mock so I can see what a UX looks

like in the editor as well.

GEORGE MOUNT: Yeah, that was one of the first things

that we talked about when we were talking to the Android

Studio team.

And they were really excited about data binding

but as you saw in the last talk, they

are working on some pretty exciting things right now.

And so they're going to get to it but they're not there yet.

YGIT BOYAR: I can tell that code completion is

coming this way of course.

That's good news for data binding.

And that's one of the things we should do.

AUDIENCE: Is it possible to inflate ViewStub

according to data binding?

GEORGE MOUNT: You said, "Inflate new stuff"?


GEORGE MOUNT: Oh yes, there's a special implementation

for ViewStub because as you saw we have final classes--

final fields.

So we have a ViewStub proxy final field for that.

Because ViewStub actually replaces itself with a view.

So we can't do a replacement for a final field, right?

So we have a ViewStub proxy and you

can get either the view, or the replaced view with it

depending on its status.

AUDIENCE: OK, and it is possible to parse the data model

to underlying [INAUDIBLE].

YGIT BOYAR: Yes, you us the same expression

as if you are passing an attribute.

And if there's a binding for that class

we know to create the binding for that class

and pass that variable as the variable changes.

AUDIENCE: Cool, thank you.

AUDIENCE: Hello, the current design tab

inside Android Studio sometimes doesn't render the layout


Sometimes there is just a black screen or something like this.

Are you going to improve this and will its work with data

binding also?

GEORGE MOUNT: So we are constantly

improving the Android Studio layout editor.

So that's going to get better and better over time,


AUDIENCE: OK, thank you.

AUDIENCE: I was wondering, you showed the image

loading examples.

How would you go about canceling the loads when

your UI is torn down somewhere.

YGIT BOYAR: You have the binding so you can model it--

so for example you create the binding--

what you are trying to cancel is--

let's say you are trying to load user image URL,

and then the image URL disappeared or--

AUDIENCE: No, I mean like in Picasso, you can pass a tag

and then cancel every loads on those tags for example,

when your fragment goes away, or something like that.

YGIT BOYAR: One option would be passing that tag as well

to the binding adapter.

Now you can cancel through Picasso.

Or if there's some variable in your model

that can control that then, again, the binding adapter

can handle the canceling.

AUDIENCE: OK, but it's not really a callback

for tearing down--

YGIT BOYAR: You can pass a callback variable if want.

For the view tearing down-- You know

when the view is going away through the Android view life

cycle, and there are callback methods

that you can assign to this binding class.

You can even say, "Hey stop, don't rebind."

The mandated changes generated by the binding class

calls your callback, "I'm about to re-bind."

And now you can say, "No.


Don't re-bind."

When you say that you are responsible to tell it

to re-bind whenever you want.

Which is very useful if you are using inside the RecyclerVIew.

Recycler gets really upset if you update the views themselves


So if you're at the RecyclerView.Adapter you say,

"Don't change. " And in runs it through the RevyclerlViews like

item change flaw.

AUDIENCE: OK, I think that answers my questions.

AUDIENCE: What's the best way to debug the expressions that

are in the view markup?

Because it's logic effectively and so if there's a bug

how do you debug it.

How do you step through it?

YGIT BOYAR: Well, you can't step through the code, the source

code that we generate is actually visible to you.

It's generated and you can step through it if you need to.

It's a little bit on the uglier side

because it's generated code, but you can't step through it.

You should try to limit what your expressions look like.

Now we have some things that make it a lot easier for you

so that you have fewer bugs.

For example, we have a no coalescent operator

which-- those of you who have used other languages

know what this is, but essentially-- says

if the first part is null then use the second part.

And we also automatically do null checks

on your expressions.

So if any part of it is null then the resultant value

is a default value.

So if you call setText on then,

if user is null, then the whole expression is null.

You'll see fewer bugs from that.

Like in the best practices you should limit your expressions

so they're not too complicated.

AUDIENCE: Yeah, I would agree.



When we're using data binding with recycle views actually,

the documentation says that we should--

after passing the view to the view holder

and get the binding clause.

We should use as a executePendingBindings.

I want to know exactly what is it like,

because I'll tried to use on list view

and didn't call that method and it worked fine.

YGIT BOYAR: It will work fine but it will-- As the variables

change we don't instantly go and update the views.

What data binding does is we just keep track

and wait until the next animation frame happens.

This helps us to run less explorations

because it's more efficient.

Plus you can change your data on any thread.

You don't need to be on the main thread

while changing your data, and data binding

will take care of moving it to the main thread.

Now the way this works is you wait until the next animation


From a recycler perspective for the list view, what it does

is it calls your own bind method and it

expects the view to be updated when that method returns.

So if there's any TextView that you

should set that takes when that happens,

otherwise the view won't be measured properly.

The first layout will be wrong.

The next animation frame, data binding will update that view.

This is going to cause another layout which

will correct itself.

It looked like it worked fine, but it actually

did two layouts for no reason.

So you by calling executePendingBindings,

you sync all the VFs with your model

so that data binding is not going to request another layout

and the ListView or RecyclerView will run the proper layout


AUDIENCE: So, the last one.

When you're importing variables such as Android-- the base

class to use an expression-- I want

to know if on the new Android Studio

we actually have error fix because if you

try to do that expression of setting visibility,

it actually shows you an error, even after compiling.

So I want to know if that's fixed on Android Studios?

GEORGE MOUNT: You said for compiling?

I shouldn't show you errors for compiling.

YGIT BOYAR: It does show an error.

It's a very detailed problem.

While parsing your expressions we use ANTLR.

We say grammer parser is very popular.

That model doesn't work on IntelliJ.

IntelliJ suggests using another parser called JFlex.

So sometimes these grammars don't match properly.

So if you have a bug just report it and we will try to fix it.

We are working on some other solutions that

will allow us to use ANTLR parser inside IntelliJ

but there are fundamental different models,

so it's not very [INAUDIBLE], but we are working

on making it more reliable.

GEORGE MOUNT: All right, thank you.



The Description of Data Binding -- Write Apps Faster (Android Dev Summit 2015)