Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

Commit 0832387

Browse files
Jetifies and updates samples (with LiveData migration) (#23)
* Jetifies and updates two-way sample * Use resources from activity + minor fixes * Updates circle to api 28 stable * Jetifies basic sample * Migrates basic sample to LiveData * Fixes typo and improves README
1 parent 273c77f commit 0832387

32 files changed

Lines changed: 226 additions & 194 deletions

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: 2
22

33
config_android: &config_android
44
docker:
5-
- image: circleci/android:api-28-alpha
5+
- image: circleci/android:api-28
66
working_directory: ~/project
77
environment:
88
JAVA_TOOL_OPTIONS: "-Xmx1024m"

BasicSample/README.md

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This sample showcases the following features of the
55
[Data Binding library](https://developer.android.com/topic/libraries/data-binding/index.html):
66

77
* Layout variables and expressions
8-
* Observability through Observable Fields and Observable classes
8+
* Observability through Observable Fields, LiveData and Observable classes
99
* Binding Adapters, Binding Methods and Binding Converters
1010
* Seamless integration with ViewModels
1111

@@ -42,9 +42,9 @@ for a simple example.
4242

4343
### Observability
4444

45-
In order to update the UI automatically when the data changes, Data Binding lets you bind attributes with
46-
observable objects. You can choose between two mechanisms to achieve this: Observable fields and
47-
Observable classes.
45+
In order to update the UI automatically when the data changes, Data Binding lets you bind attributes
46+
with observable objects. You can choose between three mechanisms to achieve this: Observable fields,
47+
LiveData and Observable classes.
4848

4949
#### Observable fields
5050

@@ -55,7 +55,7 @@ fields will update the layout automatically.
5555
```kotlin
5656
class ProfileObservableFieldsViewModel : ViewModel() {
5757

58-
val likes = ObservableInt(0)
58+
val likes = ObservableInt(0)
5959

6060
fun onLike() {
6161
likes.increment() // Equivalent to set(likes.get() + 1)
@@ -66,6 +66,35 @@ class ProfileObservableFieldsViewModel : ViewModel() {
6666
In this example, when `onLike` is called, the number of likes is incremented
6767
and the UI is updated. There is no need to notify that the property changed.
6868

69+
#### LiveData
70+
71+
LiveData is an observable from
72+
[Android Architecture Components](https://developer.android.com/topic/libraries/architecture)
73+
that is lifecycle-aware.
74+
75+
The advantages over Observable Fields are that LiveData supports
76+
[Transformations](https://developer.android.com/reference/android/arch/lifecycle/Transformations)
77+
and it's compatible with other components and libraries, like Room and WorkManager.
78+
79+
```kotlin
80+
class ProfileLiveDataViewModel : ViewModel() {
81+
private val _likes = MutableLiveData(0)
82+
val likes: LiveData<Int> = _likes // Expose an immutable LiveData
83+
84+
fun onLike() {
85+
_likes.value = (_likes.value ?: 0) + 1
86+
}
87+
}
88+
```
89+
90+
It requires an extra step done on the binding:
91+
92+
93+
```kotlin
94+
95+
binding.lifecycleOwner = this // use viewLifecycleOwner when assigning a fragment
96+
```
97+
6998
#### Observable classes
7099

71100
For maximum flexibility and control, you can implement a fully observable class and decide when

BasicSample/app/build.gradle

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ apply plugin: 'kotlin-android-extensions'
2323
apply plugin: 'kotlin-kapt'
2424

2525
android {
26-
compileSdkVersion 27
26+
compileSdkVersion rootProject.compileSdkVersion
2727
defaultConfig {
28-
applicationId "com.example.android.databinding.basicsample"
29-
minSdkVersion 17
30-
targetSdkVersion 27
28+
applicationId "com.example.android.databinding.twowaysample"
29+
minSdkVersion rootProject.minSdkVersion
30+
targetSdkVersion rootProject.targetSdkVersion
3131
versionCode 1
3232
versionName "1.0"
33-
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
33+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3434
vectorDrawables.useSupportLibrary = true
3535
}
3636
buildTypes {
@@ -46,18 +46,18 @@ android {
4646

4747
dependencies {
4848
implementation fileTree(dir: 'libs', include: ['*.jar'])
49-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
50-
implementation 'com.android.support:appcompat-v7:27.1.1'
51-
implementation 'com.android.support:support-v4:27.1.1'
52-
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
49+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
50+
implementation "androidx.appcompat:appcompat:$appCompatVersion"
51+
implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
5352

54-
// For ViewModelActivity
55-
implementation "android.arch.lifecycle:extensions:1.1.1"
53+
implementation "androidx.lifecycle:lifecycle-extensions:$archLifecycleVersion"
5654

57-
testImplementation 'junit:junit:4.12'
55+
testImplementation "junit:junit:$junitVersion"
56+
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
5857

59-
androidTestImplementation 'com.android.support.test:runner:1.0.2'
60-
androidTestImplementation 'com.android.support.test:rules:1.0.2'
61-
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
58+
androidTestImplementation "androidx.test:runner:$androidXTestVersion"
59+
androidTestImplementation "androidx.test.ext:junit:$runnerExtVersion"
60+
androidTestImplementation "androidx.test:rules:$androidXTestVersion"
61+
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
6262

6363
}

BasicSample/app/src/androidTest/java/com/example/android/databinding/basicsample/BasicUsageTest.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616

1717
package com.example.android.databinding.basicsample
1818

19-
import android.support.test.espresso.Espresso.onView
20-
import android.support.test.espresso.action.ViewActions.click
21-
import android.support.test.espresso.assertion.ViewAssertions.matches
22-
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
23-
import android.support.test.espresso.matcher.ViewMatchers.withId
24-
import android.support.test.espresso.matcher.ViewMatchers.withText
25-
import android.support.test.rule.ActivityTestRule
26-
import android.support.test.runner.AndroidJUnit4
19+
import androidx.test.espresso.Espresso.onView
20+
import androidx.test.espresso.action.ViewActions.click
21+
import androidx.test.espresso.assertion.ViewAssertions.matches
22+
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
23+
import androidx.test.espresso.matcher.ViewMatchers.withId
24+
import androidx.test.espresso.matcher.ViewMatchers.withText
25+
import androidx.test.ext.junit.runners.AndroidJUnit4
26+
import androidx.test.rule.ActivityTestRule
2727
import com.example.android.databinding.basicsample.ui.MainActivity
2828
import org.junit.Rule
2929
import org.junit.Test
@@ -39,15 +39,15 @@ import org.junit.runner.RunWith
3939
class BasicUsageTest {
4040

4141
@get:Rule
42-
var mActivityRule = ActivityTestRule(MainActivity::class.java)
42+
var activityRule = ActivityTestRule(MainActivity::class.java)
4343

4444
@Test
4545
fun observableFieldsActivity_likes() {
4646
// Click on button to open activity
4747
onView(withId(R.id.observable_fields_activity_button)).perform(click())
4848

4949
// Click Like 5 times
50-
(0 until 5).forEach {
50+
repeat(5) {
5151
onView(withId(R.id.like_button)).perform(click())
5252
}
5353

@@ -61,7 +61,7 @@ class BasicUsageTest {
6161
onView(withId(R.id.viewmodel_activity_button)).perform(click())
6262

6363
// Click Like 5 times
64-
(0 until 5).forEach {
64+
repeat(5) {
6565
onView(withId(R.id.like_button)).perform(click())
6666
}
6767

BasicSample/app/src/main/java/com/example/android/databinding/basicsample/data/ObservableFieldProfile.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.example.android.databinding.basicsample.data
1818

19-
import android.databinding.ObservableInt
19+
import androidx.databinding.ObservableInt
2020

2121
/**
2222
* Used as a layout variable to provide static properties (name and lastName) and an observable
@@ -26,4 +26,4 @@ data class ObservableFieldProfile(
2626
val name: String,
2727
val lastName: String,
2828
val likes: ObservableInt
29-
)
29+
)

BasicSample/app/src/main/java/com/example/android/databinding/basicsample/data/ProfileObservableViewModel.kt

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,49 @@
1616

1717
package com.example.android.databinding.basicsample.data
1818

19-
import android.arch.lifecycle.ViewModel
20-
import android.databinding.Bindable
21-
import android.databinding.ObservableField
22-
import android.databinding.ObservableInt
19+
import androidx.databinding.Bindable
20+
import androidx.databinding.ObservableField
21+
import androidx.databinding.ObservableInt
22+
import androidx.lifecycle.LiveData
23+
import androidx.lifecycle.MutableLiveData
24+
import androidx.lifecycle.Transformations
25+
import androidx.lifecycle.ViewModel
2326
import com.example.android.databinding.basicsample.BR
2427
import com.example.android.databinding.basicsample.util.ObservableViewModel
2528

29+
2630
/**
2731
* This class is used as a variable in the XML layout and it's fully observable, meaning that
28-
* changes to any of the public fields automatically refresh the UI.
32+
* changes to any of the exposed observables automatically refresh the UI. *
33+
*/
34+
class ProfileLiveDataViewModel : ViewModel() {
35+
private val _name = MutableLiveData("Ada")
36+
private val _lastName = MutableLiveData("Lovelace")
37+
private val _likes = MutableLiveData(0)
38+
39+
val name: LiveData<String> = _name
40+
val lastName: LiveData<String> = _lastName
41+
val likes: LiveData<Int> = _likes
42+
43+
// popularity is exposed as LiveData using a Transformation instead of a @Bindable property.
44+
val popularity: LiveData<Popularity> = Transformations.map(_likes) {
45+
when {
46+
it > 9 -> Popularity.STAR
47+
it > 4 -> Popularity.POPULAR
48+
else -> Popularity.NORMAL
49+
}
50+
}
51+
52+
fun onLike() {
53+
_likes.value = (_likes.value ?: 0) + 1
54+
}
55+
}
56+
57+
/**
58+
* As an alternative to LiveData, you can use Observable Fields and binding properties.
2959
*
30-
* `Popularity` is exposed here as a `@Bindable` attribute, see the
31-
* [ProfileObservableFieldsViewModel] for an alternative using Observable fields.
60+
* `Popularity` is exposed here as a `@Bindable` property so it's necessary to call
61+
* `notifyPropertyChanged` when any of the dependent properties change (`likes` in this case).
3262
*/
3363
class ProfileObservableViewModel : ObservableViewModel() {
3464
val name = ObservableField("Ada")
@@ -53,30 +83,6 @@ class ProfileObservableViewModel : ObservableViewModel() {
5383
}
5484
}
5585

56-
/**
57-
* As an alternative, the @Bindable attribute can be replaced with an
58-
* `ObservableField`. In this case 'popularity' is an `ObservableField` which has to be computed when
59-
* `likes` change.
60-
*/
61-
class ProfileObservableFieldsViewModel : ViewModel() {
62-
val name = ObservableField("Ada")
63-
val lastName = ObservableField("Lovelace")
64-
val likes = ObservableInt(0)
65-
66-
// popularity is exposed as an ObservableField instead of a @Bindable property.
67-
val popularity = ObservableField<Popularity>(Popularity.NORMAL)
68-
69-
fun onLike() {
70-
likes.set(likes.get() + 1)
71-
72-
popularity.set(likes.get().let {
73-
if (it > 9) Popularity.STAR
74-
if (it > 4) Popularity.POPULAR
75-
Popularity.NORMAL
76-
})
77-
}
78-
}
79-
8086
enum class Popularity {
8187
NORMAL,
8288
POPULAR,

BasicSample/app/src/main/java/com/example/android/databinding/basicsample/ui/MainActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
package com.example.android.databinding.basicsample.ui
1818

1919
import android.content.Intent
20-
import android.databinding.DataBindingUtil
20+
import androidx.databinding.DataBindingUtil
2121
import android.os.Bundle
22-
import android.support.v7.app.AppCompatActivity
22+
import androidx.appcompat.app.AppCompatActivity
2323
import com.example.android.databinding.basicsample.R
2424
import com.example.android.databinding.basicsample.databinding.ActivityMainBinding
2525

BasicSample/app/src/main/java/com/example/android/databinding/basicsample/ui/ObservableFieldActivity.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
package com.example.android.databinding.basicsample.ui
1818

19-
import android.databinding.DataBindingUtil
20-
import android.databinding.ObservableInt
2119
import android.os.Bundle
22-
import android.support.v7.app.AppCompatActivity
2320
import android.view.View
21+
import androidx.appcompat.app.AppCompatActivity
22+
import androidx.databinding.DataBindingUtil
23+
import androidx.databinding.ObservableInt
2424
import com.example.android.databinding.basicsample.R
2525
import com.example.android.databinding.basicsample.data.ObservableFieldProfile
2626
import com.example.android.databinding.basicsample.databinding.ObservableFieldProfileBinding

BasicSample/app/src/main/java/com/example/android/databinding/basicsample/ui/ViewModelActivity.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616

1717
package com.example.android.databinding.basicsample.ui
1818

19-
import android.arch.lifecycle.ViewModelProviders
20-
import android.databinding.DataBindingUtil
2119
import android.os.Bundle
22-
import android.support.v7.app.AppCompatActivity
20+
import androidx.appcompat.app.AppCompatActivity
21+
import androidx.databinding.DataBindingUtil
22+
import androidx.lifecycle.ViewModelProviders
2323
import com.example.android.databinding.basicsample.R
24-
import com.example.android.databinding.basicsample.data.ProfileObservableViewModel
24+
import com.example.android.databinding.basicsample.data.ProfileLiveDataViewModel
2525
import com.example.android.databinding.basicsample.databinding.ViewmodelProfileBinding
2626

2727
/**
@@ -36,13 +36,19 @@ class ViewModelActivity : AppCompatActivity() {
3636
super.onCreate(savedInstanceState)
3737

3838
// Obtain ViewModel from ViewModelProviders
39-
val viewModel = ViewModelProviders.of(this).get(ProfileObservableViewModel::class.java)
39+
val viewModel = ViewModelProviders.of(this).get(ProfileLiveDataViewModel::class.java)
40+
41+
// An alternative ViewModel using Observable fields and @Bindable properties can be used:
42+
// val viewModel = ViewModelProviders.of(this).get(ProfileObservableViewModel::class.java)
4043

4144
// Obtain binding
4245
val binding: ViewmodelProfileBinding =
4346
DataBindingUtil.setContentView(this, R.layout.viewmodel_profile)
4447

4548
// Bind layout with ViewModel
4649
binding.viewmodel = viewModel
50+
51+
// LiveData needs the lifecycle owner
52+
binding.lifecycleOwner = this
4753
}
4854
}

BasicSample/app/src/main/java/com/example/android/databinding/basicsample/util/BindingAdapters.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ package com.example.android.databinding.basicsample.util
1818

1919
import android.content.Context
2020
import android.content.res.ColorStateList
21-
import android.databinding.BindingAdapter
21+
import androidx.databinding.BindingAdapter
2222
import android.graphics.drawable.Drawable
2323
import android.os.Build
24-
import android.support.v4.content.ContextCompat
25-
import android.support.v4.widget.ImageViewCompat
24+
import androidx.core.content.ContextCompat
25+
import androidx.core.widget.ImageViewCompat
2626
import android.view.View
2727
import android.widget.ImageView
2828
import android.widget.ProgressBar
@@ -101,4 +101,4 @@ object BindingAdapters {
101101
}
102102
}
103103
}
104-
}
104+
}

0 commit comments

Comments
 (0)