├── .github ├── ci-gradle.properties └── workflows │ ├── BasicSample.yaml │ ├── TwoWaySample.yaml │ └── copy-branch.yml ├── BasicSample ├── .gitignore ├── .google │ └── packaging.yaml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ └── databinding │ │ │ └── basicsample │ │ │ └── BasicUsageTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── android │ │ │ │ └── databinding │ │ │ │ └── basicsample │ │ │ │ ├── data │ │ │ │ ├── ObservableFieldProfile.kt │ │ │ │ └── ProfileObservableViewModel.kt │ │ │ │ ├── ui │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── ObservableFieldActivity.kt │ │ │ │ └── ViewModelActivity.kt │ │ │ │ └── util │ │ │ │ ├── BindingAdapters.kt │ │ │ │ ├── BindingConverters.kt │ │ │ │ ├── BindingMethods.kt │ │ │ │ └── ObservableViewModel.kt │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_person_black_96dp.xml │ │ │ └── ic_whatshot_black_96dp.xml │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ ├── observable_field_profile.xml │ │ │ └── viewmodel_profile.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── android │ │ └── databinding │ │ └── basicsample │ │ └── data │ │ └── ProfileObservableViewModelTest.kt ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots │ └── screenshotbasic.png └── settings.gradle ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TwoWaySample ├── .gitignore ├── .google │ └── packaging.yaml ├── README.md ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ └── databinding │ │ │ └── twowaysample │ │ │ └── BasicUsageTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── android │ │ │ │ └── databinding │ │ │ │ └── twowaysample │ │ │ │ ├── data │ │ │ │ ├── IntervalTimerViewModel.kt │ │ │ │ └── IntervalTimerViewModelFactory.kt │ │ │ │ ├── ui │ │ │ │ ├── AnimationBindingAdapters.kt │ │ │ │ ├── BindingAdapters.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ └── NumberOfSetsBindingAdapters.kt │ │ │ │ └── util │ │ │ │ ├── Converter.kt │ │ │ │ ├── ObservableViewModel.kt │ │ │ │ └── TimerWrapper.kt │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ └── interval_timer.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ ├── styles.xml │ │ │ └── tags.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── android │ │ └── databinding │ │ └── twowaysample │ │ └── data │ │ └── IntervalTimerViewModelTest.kt ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots │ └── screenshot2way.png └── settings.gradle └── scripts └── checksum.sh /.github/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | org.gradle.daemon=false 18 | org.gradle.parallel=true 19 | org.gradle.jvmargs=-Xmx5120m 20 | org.gradle.workers.max=2 21 | 22 | kotlin.incremental=false 23 | kotlin.compiler.execution.strategy=in-process 24 | -------------------------------------------------------------------------------- /.github/workflows/BasicSample.yaml: -------------------------------------------------------------------------------- 1 | name: BasicSample 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/BasicSample.yaml' 9 | - 'BasicSample/**' 10 | pull_request: 11 | paths: 12 | - '.github/workflows/BasicSample.yaml' 13 | - 'BasicSample/**' 14 | 15 | env: 16 | SAMPLE_PATH: BasicSample 17 | 18 | jobs: 19 | build_and_test: 20 | runs-on: macOS-latest 21 | timeout-minutes: 30 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | 27 | - name: Copy CI gradle.properties 28 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 29 | 30 | - name: Set up JDK 17 31 | uses: actions/setup-java@v4 32 | with: 33 | distribution: 'zulu' 34 | java-version: 17 35 | 36 | - name: Generate cache key 37 | run: ./scripts/checksum.sh $SAMPLE_PATH checksum.txt 38 | 39 | - uses: actions/cache@v3 40 | with: 41 | path: | 42 | ~/.gradle/caches/modules-* 43 | ~/.gradle/caches/jars-* 44 | ~/.gradle/caches/build-cache-* 45 | key: gradle-${{ hashFiles('checksum.txt') }} 46 | 47 | - name: Build project 48 | working-directory: ${{ env.SAMPLE_PATH }} 49 | run: ./gradlew assembleDebug lintDebug --stacktrace 50 | 51 | - name: Run instrumentation tests 52 | uses: reactivecircus/android-emulator-runner@v2 53 | with: 54 | api-level: 26 55 | arch: x86 56 | disable-animations: true 57 | script: ./gradlew connectedCheck --stacktrace 58 | working-directory: ${{ env.SAMPLE_PATH }} 59 | 60 | - name: Upload build outputs (APKs) 61 | uses: actions/upload-artifact@v3 62 | with: 63 | name: build-outputs-BasicSample 64 | path: ${{ env.SAMPLE_PATH }}/app/build/outputs 65 | 66 | - name: Upload build reports 67 | if: always() 68 | uses: actions/upload-artifact@v3 69 | with: 70 | name: build-reports-BasicSample 71 | path: ${{ env.SAMPLE_PATH }}/app/build/reports 72 | -------------------------------------------------------------------------------- /.github/workflows/TwoWaySample.yaml: -------------------------------------------------------------------------------- 1 | name: TwoWaySample 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/TwoWaySample.yaml' 9 | - 'TwoWaySample/**' 10 | pull_request: 11 | paths: 12 | - '.github/workflows/TwoWaySample.yaml' 13 | - 'TwoWaySample/**' 14 | 15 | env: 16 | SAMPLE_PATH: TwoWaySample 17 | 18 | jobs: 19 | build_and_test: 20 | runs-on: macOS-latest 21 | timeout-minutes: 30 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | 27 | - name: Copy CI gradle.properties 28 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 29 | 30 | - name: Set up JDK 11 31 | uses: actions/setup-java@v3 32 | with: 33 | distribution: 'zulu' 34 | java-version: 11 35 | 36 | - name: Generate cache key 37 | run: ./scripts/checksum.sh $SAMPLE_PATH checksum.txt 38 | 39 | - uses: actions/cache@v3 40 | with: 41 | path: | 42 | ~/.gradle/caches/modules-* 43 | ~/.gradle/caches/jars-* 44 | ~/.gradle/caches/build-cache-* 45 | key: gradle-${{ hashFiles('checksum.txt') }} 46 | 47 | - name: Build project 48 | working-directory: ${{ env.SAMPLE_PATH }} 49 | run: ./gradlew assembleDebug lintDebug --stacktrace 50 | 51 | - name: Run instrumentation tests 52 | uses: reactivecircus/android-emulator-runner@v2 53 | with: 54 | api-level: 26 55 | arch: x86 56 | disable-animations: true 57 | script: ./gradlew connectedCheck --stacktrace 58 | working-directory: ${{ env.SAMPLE_PATH }} 59 | 60 | - name: Upload build outputs (APKs) 61 | uses: actions/upload-artifact@v3 62 | with: 63 | name: build-outputs-TwoWaySample 64 | path: ${{ env.SAMPLE_PATH }}/app/build/outputs 65 | 66 | - name: Upload build reports 67 | if: always() 68 | uses: actions/upload-artifact@v3 69 | with: 70 | name: build-reports-TwoWaySample 71 | path: ${{ env.SAMPLE_PATH }}/app/build/reports -------------------------------------------------------------------------------- /.github/workflows/copy-branch.yml: -------------------------------------------------------------------------------- 1 | # Duplicates default main branch to the old master branch 2 | 3 | name: Duplicates main to old master branch 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the main branch 7 | on: 8 | push: 9 | branches: [ main ] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "copy-branch" 14 | copy-branch: 15 | # The type of runner that the job will run on 16 | runs-on: ubuntu-latest 17 | 18 | # Steps represent a sequence of tasks that will be executed as part of the job 19 | steps: 20 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it, 21 | # but specifies master branch (old default). 22 | - uses: actions/checkout@v2 23 | with: 24 | fetch-depth: 0 25 | ref: master 26 | 27 | - run: | 28 | git config user.name github-actions 29 | git config user.email github-actions@github.com 30 | git merge origin/main 31 | git push 32 | -------------------------------------------------------------------------------- /BasicSample/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /BasicSample/.google/packaging.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # GOOGLE SAMPLE PACKAGING DATA 17 | # 18 | # This file is used by Google as part of our samples packaging process. 19 | # End users may safely ignore this file. It has no relevance to other systems. 20 | --- 21 | # Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} 22 | status: PUBLISHED 23 | 24 | # Optional, put additional explanation here for DEPRECATED or SUPERCEDED. 25 | # statusNote: 26 | 27 | # See http://go/sample-categories 28 | technologies: [Android] 29 | categories: [Data Binding] 30 | languages: [Kotlin] 31 | solutions: [Mobile] 32 | 33 | # May be omitted if unpublished 34 | github: googlesamples/android-databinding 35 | 36 | # Values: BEGINNER | INTERMEDIATE | ADVANCED | EXPERT 37 | level: INTERMEDIATE 38 | 39 | # Default: apache2. May be omitted for most samples. 40 | # Alternatives: apache2-android (for AOSP) 41 | license: apache2 42 | -------------------------------------------------------------------------------- /BasicSample/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement (CLA). 9 | 10 | * If you are an individual writing original source code and you're sure you 11 | own the intellectual property, then you'll need to sign an [individual CLA] 12 | (https://cla.developers.google.com). 13 | * If you work for a company that wants to allow you to contribute your work, 14 | then you'll need to sign a [corporate CLA] 15 | (https://cla.developers.google.com). 16 | * Please make sure you sign both, Android and Google CLA 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the repo in question. 25 | 1. The repo owner will respond to your issue promptly. 26 | 1. If your proposed change is accepted, and you haven't already done so, sign a 27 | Contributor License Agreement (see details above). 28 | 1. Fork the desired repo, develop and test your code changes. 29 | 1. Ensure that your code adheres to the existing style in the sample to which 30 | you are contributing. Refer to the 31 | [Android Code Style Guide] 32 | (https://source.android.com/source/code-style.html) for the 33 | recommended coding standards for this organization. 34 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 35 | 1. Submit a pull request. 36 | -------------------------------------------------------------------------------- /BasicSample/README.md: -------------------------------------------------------------------------------- 1 | Android Data Binding Basic Sample 2 | ============================================= 3 | 4 | This sample showcases the following features of the 5 | [Data Binding library](https://developer.android.com/topic/libraries/data-binding/index.html): 6 | 7 | * Layout variables and expressions 8 | * Observability through Observable Fields, LiveData and Observable classes 9 | * Binding Adapters, Binding Methods and Binding Converters 10 | * Seamless integration with ViewModels 11 | 12 | It shows common bad practices and their solutions in two different screens. 13 | 14 | Features 15 | -------- 16 | 17 | ![Screenshot](https://github.com/googlesamples/android-databinding/blob/master/BasicSample/screenshots/screenshotbasic.png) 18 | 19 | ### Layout variables and expressions 20 | 21 | With Data Binding you can write less boilerplate and repetitive code. It moves UI operations out 22 | of the activities and fragments to the XML layout. 23 | 24 | For example, instead of setting text on a TextView in an activity: 25 | 26 | ```java 27 | TextView textView = findViewById(R.id.name); 28 | textView.setText(user.name); 29 | ``` 30 | 31 | You assign the attribute to a variable, in the XML layout: 32 | 33 | ```xml 34 | 38 | ``` 39 | 40 | See `ObservableFieldActivity.kt`, `ObservableFieldProfile.kt` and `observable_field_profile.xml` 41 | for a simple example. 42 | 43 | ### Observability 44 | 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. 48 | 49 | #### Observable fields 50 | 51 | Types like ObservableBoolean, ObservableInt and the generic ObservableField replace the 52 | corresponding primitives to make them observable. Setting a new value on one of the Observable 53 | fields will update the layout automatically. 54 | 55 | ```kotlin 56 | class ProfileObservableFieldsViewModel : ViewModel() { 57 | 58 | val likes = ObservableInt(0) 59 | 60 | fun onLike() { 61 | likes.increment() // Equivalent to set(likes.get() + 1) 62 | } 63 | } 64 | ``` 65 | 66 | In this example, when `onLike` is called, the number of likes is incremented 67 | and the UI is updated. There is no need to notify that the property changed. 68 | 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 = _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 | 98 | #### Observable classes 99 | 100 | For maximum flexibility and control, you can implement a fully observable class and decide when 101 | to update certain properties. This lets you create dependencies between properties and it's 102 | useful to dispatch partial UI updates, for example avoiding 103 | potential glitches (UI elements updating almost at the same time). 104 | 105 | ```kotlin 106 | class ProfileObservableViewModel : ObservableViewModel() { 107 | val likes = ObservableInt(0) 108 | 109 | fun onLike() { 110 | likes.increment() 111 | notifyPropertyChanged(BR.popularity) 112 | } 113 | 114 | @Bindable 115 | fun getPopularity(): Popularity { 116 | return likes.get().let { 117 | when { 118 | it > 9 -> Popularity.STAR 119 | it > 4 -> Popularity.POPULAR 120 | else -> Popularity.NORMAL 121 | } 122 | } 123 | } 124 | } 125 | ``` 126 | 127 | In this example, when `onLike` is called, the number of likes is incremented and the 128 | `popularity` property is notified of a potential change (`popularity` depends on `likes`). 129 | `getPopularity` is called by the library, returning a possible new value. 130 | 131 | See `ProfileObservableFieldsViewModel.kt` for a complete example. 132 | 133 | #### Binding adapters 134 | 135 | Binding adapters let you customize or create layout attributes. For example, you can create 136 | an `app:progressTint` attribute for progress bars where you change the color of the 137 | progress indicator depending on an external value. 138 | 139 | ```kotlin 140 | @BindingAdapter("app:progressTint") 141 | @JvmStatic fun tintPopularity(view: ProgressBar, popularity: Popularity) { 142 | 143 | val color = getAssociatedColor(popularity, view.context) 144 | view.progressTintList = ColorStateList.valueOf(color) 145 | } 146 | ``` 147 | 148 | The binding is created in the XML layout with: 149 | 150 | ```xml 151 | 153 | ``` 154 | 155 | Using binding adapters lets you move UI calls from the activity to static methods, improving 156 | encapsulation. 157 | 158 | You can also use multiple attributes in a Binding Adapter, see `viewmodel_profile.xml` for a complete 159 | example. 160 | 161 | #### Binding methods and binding converters 162 | 163 | Binding methods and binding converters let you reduce code if your binding adapters 164 | are very simple. You can read about them in the 165 | [official guide](https://developer.android.com/topic/libraries/data-binding/index.html#attribute_setters). 166 | 167 | For example, if an attribute's value needs to be passed to a method in the class: 168 | 169 | ```kotlin 170 | @BindingAdapter("app:srcCompat") 171 | @JvmStatic fun srcCompat(view: ImageView, @DrawableRes drawableId: Int) { 172 | view.setImageResource(drawable) 173 | } 174 | ``` 175 | 176 | You can replace this with a Binding Method which can be added to any class in the project: 177 | 178 | ```kotlin 179 | @BindingMethods( 180 | BindingMethod(type = ImageView::class, 181 | attribute = "app:srcCompat", 182 | method = "setImageDrawable")) 183 | ``` 184 | 185 | #### Binding Converters (use with caution) 186 | 187 | In this sample we show a View depending on whether a number is zero. There are many options to do 188 | this. We're showing two, one in the `observable_field_profile.xml` 189 | and, the recommended way, in `viewmodel_profile.xml`. 190 | 191 | The goal is to bind the view's visibility to the number of likes, but this won't work: 192 | 193 | ```xml 194 | android:visibility="@{viewmodel.likes}" 195 | ``` 196 | 197 | The number of likes is an integer and the visibility attribute takes an integer 198 | (`VISIBLE`, `GONE` and `INVISIBLE` are 0, 4 and 8 respectively), so doing this would build, 199 | but the result would not be the expected. 200 | 201 | A possible solution is: 202 | 203 | ```xml 204 | android:visibility="@{viewmodel.likes == 0 ? View.GONE : View.VISIBLE}"/ 205 | ``` 206 | 207 | But it adds a relatively complex expression to the layout. 208 | 209 | Instead, you can create and import a utils class: 210 | 211 | ```xml 212 | 213 | 214 | ... 215 | 216 | ``` 217 | 218 | and use it from the View like so: 219 | 220 | ```xml 221 | android:visibility="@{ConverterUtil.isZero(viewmodel.likes)}" 222 | ``` 223 | 224 | `isZero` returns a boolean and `visibility` takes an integer so in order to convert 225 | from boolean we can also define a BindingConversion: 226 | 227 | ```kotlin 228 | @BindingConversion 229 | @JvmStatic fun booleanToVisibility(isVisible: Boolean): Int { // Risky! applies everywhere 230 | return if (isVisible) View.VISIBLE else View.GONE 231 | } 232 | ``` 233 | 234 | This conversion is unsafe because this binding conversion is not restricted to our case: it will 235 | convert all booleans to visibility integers when the attribute takes an integer. 236 | 237 | Solution: As with every BindingConversion and BindingMethod, you can replace it with a Binding 238 | Adapter, which normally is much simpler: 239 | 240 | ```kotlin 241 | @BindingAdapter("app:hideIfZero") // Recommended solution 242 | @JvmStatic fun hideIfZero(view: View, number: Int) { 243 | view.visibility = if (number == 0) View.GONE else View.VISIBLE 244 | } 245 | ``` 246 | 247 | and as shown in in `viewmodel_profile.xml`: 248 | 249 | ```xml 250 | app:hideIfZero="@{viewmodel.likes}" 251 | ``` 252 | 253 | This defines a new custom attribute `hideIfZero` that can't be used accidentally. 254 | 255 | As a rule of thumb it's preferable to create your our custom attributes using Data Binding adapters 256 | instead of adding logic to your binding expressions. 257 | 258 | Sample app 259 | ---------- 260 | 261 | This app shows a user's profile using two different screens to showcase different Data Binding 262 | features: 263 | 264 | * Main activity: Shows how a Data Binding layout lets you access Views without `findViewById`. 265 | 266 | * Observable field activity: In this screen the user can give "likes" to the profile and the UI 267 | reacts automatically to changes. However, the activity holds the logic that receives the user 268 | click and the actual profile data, which is not testable. 269 | Also, likes are reset when the user rotates the device and the layout contains documented common 270 | bad practices. 271 | 272 | * ViewModel activity: Using a ViewModel from the 273 | [Architecture Components](https://developer.android.com/topic/libraries/architecture/index.html) 274 | fixes the rotation problem and moves logic out of the activity. Also, the use of binding 275 | adapters changes the responsibility of the activity which is no longer the "view" and 276 | solely responsible for dealing with the lifecycle. Two ViewModels are suggested in 277 | `ProfileObservableViewModel.kt`: one based on observable fields and another implementing the 278 | observable interface. 279 | 280 | 281 | License 282 | -------- 283 | 284 | Copyright 2018 The Android Open Source Project, Inc. 285 | 286 | Licensed to the Apache Software Foundation (ASF) under one or more contributor 287 | license agreements. See the NOTICE file distributed with this work for 288 | additional information regarding copyright ownership. The ASF licenses this 289 | file to you under the Apache License, Version 2.0 (the "License"); you may not 290 | use this file except in compliance with the License. You may obtain a copy of 291 | the License at 292 | 293 | http://www.apache.org/licenses/LICENSE-2.0 294 | 295 | Unless required by applicable law or agreed to in writing, software 296 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 297 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 298 | License for the specific language governing permissions and limitations under 299 | the License. 300 | -------------------------------------------------------------------------------- /BasicSample/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /BasicSample/app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'com.android.application' 18 | 19 | apply plugin: 'kotlin-android' 20 | 21 | apply plugin: 'kotlin-kapt' 22 | 23 | android { 24 | namespace 'com.example.android.databinding.basicsample' 25 | compileSdkVersion rootProject.compileSdkVersion 26 | defaultConfig { 27 | applicationId "com.example.android.databinding.basicsample" 28 | minSdkVersion rootProject.minSdkVersion 29 | targetSdkVersion rootProject.targetSdkVersion 30 | versionCode 1 31 | versionName "1.0" 32 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 33 | vectorDrawables.useSupportLibrary = true 34 | } 35 | buildTypes { 36 | release { 37 | minifyEnabled false 38 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 39 | } 40 | } 41 | buildFeatures { 42 | dataBinding true 43 | } 44 | compileOptions { 45 | sourceCompatibility JavaVersion.VERSION_17 46 | targetCompatibility JavaVersion.VERSION_17 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation fileTree(dir: 'libs', include: ['*.jar']) 52 | implementation "androidx.appcompat:appcompat:$appCompatVersion" 53 | implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion" 54 | implementation("androidx.activity:activity-ktx:$activityVersion") 55 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$archLifecycleVersion" 56 | 57 | testImplementation "junit:junit:$junitVersion" 58 | testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion" 59 | 60 | androidTestImplementation "androidx.test:runner:$androidXTestRunnerVersion" 61 | androidTestImplementation "androidx.test.ext:junit:$androidXTestExtVersion" 62 | androidTestImplementation "androidx.test:rules:$androidXTestRulesVersion" 63 | androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" 64 | 65 | } 66 | -------------------------------------------------------------------------------- /BasicSample/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /BasicSample/app/src/androidTest/java/com/example/android/databinding/basicsample/BasicUsageTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample 18 | 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 27 | import com.example.android.databinding.basicsample.ui.MainActivity 28 | import org.junit.Rule 29 | import org.junit.Test 30 | import org.junit.runner.RunWith 31 | 32 | 33 | /** 34 | * Instrumented test, which will execute on an Android device. 35 | * 36 | * See [testing documentation](http://d.android.com/tools/testing). 37 | */ 38 | @RunWith(AndroidJUnit4::class) 39 | class BasicUsageTest { 40 | 41 | @get:Rule 42 | var activityRule = ActivityTestRule(MainActivity::class.java) 43 | 44 | @Test 45 | fun observableFieldsActivity_likes() { 46 | // Click on button to open activity 47 | onView(withId(R.id.observable_fields_activity_button)).perform(click()) 48 | 49 | // Click Like 5 times 50 | repeat(5) { 51 | onView(withId(R.id.like_button)).perform(click()) 52 | } 53 | 54 | // Check that the number of likes is displayed 55 | onView(withId(R.id.likes)).check(matches(withText("5"))) 56 | } 57 | 58 | @Test 59 | fun viewmodelActivity_likes() { 60 | // Click on button to open activity 61 | onView(withId(R.id.viewmodel_activity_button)).perform(click()) 62 | 63 | // Click Like 5 times 64 | repeat(5) { 65 | onView(withId(R.id.like_button)).perform(click()) 66 | } 67 | 68 | // Check that the number of likes is displayed and the progressBar appeared 69 | onView(withId(R.id.likes)).check(matches(withText("5"))) 70 | onView(withId(R.id.progressBar)).check(matches(isDisplayed())) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/data/ObservableFieldProfile.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.data 18 | 19 | import androidx.databinding.ObservableInt 20 | 21 | /** 22 | * Used as a layout variable to provide static properties (name and lastName) and an observable 23 | * one (likes). 24 | */ 25 | data class ObservableFieldProfile( 26 | val name: String, 27 | val lastName: String, 28 | val likes: ObservableInt 29 | ) 30 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/data/ProfileObservableViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.data 18 | 19 | import androidx.databinding.Bindable 20 | import androidx.databinding.ObservableField 21 | import androidx.databinding.ObservableInt 22 | import androidx.lifecycle.LiveData 23 | import androidx.lifecycle.map 24 | import androidx.lifecycle.MutableLiveData 25 | import androidx.lifecycle.ViewModel 26 | import com.example.android.databinding.basicsample.BR 27 | import com.example.android.databinding.basicsample.util.ObservableViewModel 28 | 29 | 30 | /** 31 | * This class is used as a variable in the XML layout and it's fully observable, meaning that 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 = _name 40 | val lastName: LiveData = _lastName 41 | val likes: LiveData = _likes 42 | 43 | // popularity is exposed as LiveData using a Transformation instead of a @Bindable property. 44 | val popularity: LiveData = _likes.map { 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. 59 | * 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). 62 | */ 63 | class ProfileObservableViewModel : ObservableViewModel() { 64 | val name = ObservableField("Ada") 65 | val lastName = ObservableField("Lovelace") 66 | val likes = ObservableInt(0) 67 | 68 | fun onLike() { 69 | likes.increment() 70 | // You control when the @Bindable properties are updated using `notifyPropertyChanged()`. 71 | notifyPropertyChanged(BR.popularity) 72 | } 73 | 74 | @Bindable 75 | fun getPopularity(): Popularity { 76 | return likes.get().let { 77 | when { 78 | it > 9 -> Popularity.STAR 79 | it > 4 -> Popularity.POPULAR 80 | else -> Popularity.NORMAL 81 | } 82 | } 83 | } 84 | } 85 | 86 | enum class Popularity { 87 | NORMAL, 88 | POPULAR, 89 | STAR 90 | } 91 | 92 | private fun ObservableInt.increment() { 93 | set(get() + 1) 94 | } 95 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.ui 18 | 19 | import android.content.Intent 20 | import androidx.databinding.DataBindingUtil 21 | import android.os.Bundle 22 | import androidx.appcompat.app.AppCompatActivity 23 | import com.example.android.databinding.basicsample.R 24 | import com.example.android.databinding.basicsample.databinding.ActivityMainBinding 25 | 26 | /** 27 | * Shows a menu. 28 | */ 29 | class MainActivity : AppCompatActivity() { 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | 34 | // The layout for this activity is a Data Binding layout so it needs to be inflated using 35 | // DataBindingUtil. 36 | val binding: ActivityMainBinding = DataBindingUtil.setContentView( 37 | this, R.layout.activity_main) 38 | 39 | // The returned binding has references to all the Views with an ID. 40 | binding.observableFieldsActivityButton.setOnClickListener { 41 | startActivity(Intent(this, ObservableFieldActivity::class.java)) 42 | } 43 | binding.viewmodelActivityButton.setOnClickListener { 44 | startActivity(Intent(this, ViewModelActivity::class.java)) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/ui/ObservableFieldActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.ui 18 | 19 | import android.os.Bundle 20 | import android.view.View 21 | import android.widget.ImageView 22 | import androidx.annotation.ColorInt 23 | import androidx.appcompat.app.AppCompatActivity 24 | import androidx.databinding.BindingAdapter 25 | import androidx.databinding.DataBindingUtil 26 | import androidx.databinding.ObservableInt 27 | import com.example.android.databinding.basicsample.R 28 | import com.example.android.databinding.basicsample.data.ObservableFieldProfile 29 | import com.example.android.databinding.basicsample.databinding.ObservableFieldProfileBinding 30 | 31 | /** 32 | * This activity shows shows static data and lets the user increment the 33 | * number of likes by clicking a button. See [ViewModelActivity] for a better implementation. 34 | */ 35 | class ObservableFieldActivity : AppCompatActivity() { 36 | 37 | private val observableFieldProfile = ObservableFieldProfile("Ada", "Lovelace", ObservableInt(0)) 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | 42 | val binding: ObservableFieldProfileBinding = 43 | DataBindingUtil.setContentView(this, R.layout.observable_field_profile) 44 | binding.user = observableFieldProfile 45 | } 46 | 47 | /** 48 | * This method is triggered by the `android:onclick` attribute in the layout. It puts business 49 | * logic in the activity, which is not ideal. See {@link ViewModelActivity} for a better 50 | * solution. 51 | */ 52 | fun onLike(view: View) { 53 | observableFieldProfile.likes.set(observableFieldProfile.likes.get() + 1) 54 | } 55 | } 56 | 57 | /** 58 | * Adds support for binding tints to AppCompatImageView. 59 | * See related [bug](https://b.corp.google.com/issues/152953070?pli=1) 60 | */ 61 | @BindingAdapter("app:tint") 62 | fun ImageView.setImageTint(@ColorInt color: Int) { 63 | setColorFilter(color) 64 | } -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/ui/ViewModelActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.ui 18 | 19 | import android.os.Bundle 20 | import androidx.activity.viewModels 21 | import androidx.appcompat.app.AppCompatActivity 22 | import androidx.databinding.DataBindingUtil 23 | import com.example.android.databinding.basicsample.R 24 | import com.example.android.databinding.basicsample.data.ProfileLiveDataViewModel 25 | import com.example.android.databinding.basicsample.databinding.ViewmodelProfileBinding 26 | 27 | /** 28 | * This activity uses a [androidx.lifecycle.ViewModel] to hold the data and respond to user 29 | * actions. Also, the layout uses [androidx.databinding.BindingAdapter]s instead of expressions 30 | * which are much more powerful. 31 | * 32 | * @see com.example.android.databinding.basicsample.util.BindingAdapters 33 | */ 34 | class ViewModelActivity : AppCompatActivity() { 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | 38 | // Obtain ViewModel from ViewModelProviders 39 | val viewModel by viewModels() 40 | 41 | // An alternative ViewModel using Observable fields and @Bindable properties can be used: 42 | // val viewModel = ViewModelProviders.of(this).get(ProfileObservableViewModel::class.java) 43 | 44 | // Obtain binding 45 | val binding: ViewmodelProfileBinding = 46 | DataBindingUtil.setContentView(this, R.layout.viewmodel_profile) 47 | 48 | // Bind layout with ViewModel 49 | binding.viewmodel = viewModel 50 | 51 | // LiveData needs the lifecycle owner 52 | binding.lifecycleOwner = this 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/util/BindingAdapters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.util 18 | 19 | import android.content.Context 20 | import android.content.res.ColorStateList 21 | import androidx.databinding.BindingAdapter 22 | import android.graphics.drawable.Drawable 23 | import android.os.Build 24 | import androidx.core.content.ContextCompat 25 | import androidx.core.widget.ImageViewCompat 26 | import android.view.View 27 | import android.widget.ImageView 28 | import android.widget.ProgressBar 29 | import com.example.android.databinding.basicsample.R 30 | import com.example.android.databinding.basicsample.data.Popularity 31 | 32 | 33 | object BindingAdapters { 34 | /** 35 | * A Binding Adapter that is called whenever the value of the attribute `app:popularityIcon` 36 | * changes. Receives a popularity level that determines the icon and tint color to use. 37 | */ 38 | @BindingAdapter("app:popularityIcon") 39 | @JvmStatic fun popularityIcon(view: ImageView, popularity: Popularity) { 40 | 41 | val color = getAssociatedColor(popularity, view.context) 42 | 43 | ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color)) 44 | 45 | view.setImageDrawable(getDrawablePopularity(popularity, view.context)) 46 | } 47 | 48 | /** 49 | * A Binding Adapter that is called whenever the value of the attribute `android:progressTint` 50 | * changes. Depending on the value it determines the color of the progress bar. 51 | */ 52 | @BindingAdapter("app:progressTint") 53 | @JvmStatic fun tintPopularity(view: ProgressBar, popularity: Popularity) { 54 | 55 | val color = getAssociatedColor(popularity, view.context) 56 | 57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 58 | view.progressTintList = ColorStateList.valueOf(color) 59 | } 60 | } 61 | 62 | /** 63 | * Sets the value of the progress bar so that 5 likes will fill it up. 64 | * 65 | * Showcases Binding Adapters with multiple attributes. Note that this adapter is called 66 | * whenever any of the attribute changes. 67 | */ 68 | @BindingAdapter(value = ["app:progressScaled", "android:max"], requireAll = true) 69 | @JvmStatic fun setProgress(progressBar: ProgressBar, likes: Int, max: Int) { 70 | progressBar.progress = (likes * max / 5).coerceAtMost(max) 71 | } 72 | 73 | /** 74 | * Unused Binding Adapter to replace the Binding Converter that hides a view if the number 75 | * of likes is zero. 76 | */ 77 | @BindingAdapter("app:hideIfZero") 78 | @JvmStatic fun hideIfZero(view: View, number: Int) { 79 | view.visibility = if (number == 0) View.GONE else View.VISIBLE 80 | } 81 | 82 | private fun getAssociatedColor(popularity: Popularity, context: Context): Int { 83 | return when (popularity) { 84 | Popularity.NORMAL -> context.theme.obtainStyledAttributes( 85 | intArrayOf(android.R.attr.colorForeground)).getColor(0, 0x000000) 86 | Popularity.POPULAR -> ContextCompat.getColor(context, R.color.popular) 87 | Popularity.STAR -> ContextCompat.getColor(context, R.color.star) 88 | } 89 | } 90 | 91 | private fun getDrawablePopularity(popularity: Popularity, context: Context): Drawable? { 92 | return when (popularity) { 93 | Popularity.NORMAL -> { 94 | ContextCompat.getDrawable(context, R.drawable.ic_person_black_96dp) 95 | } 96 | Popularity.POPULAR -> { 97 | ContextCompat.getDrawable(context, R.drawable.ic_whatshot_black_96dp) 98 | } 99 | Popularity.STAR -> { 100 | ContextCompat.getDrawable(context, R.drawable.ic_whatshot_black_96dp) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/util/BindingConverters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.util 18 | 19 | import androidx.databinding.BindingConversion 20 | import android.view.View 21 | 22 | /** 23 | * In order to show a View only when it has more than 0 likes, we pass this expression to its 24 | * visibilty property: 25 | * 26 | * `android:visibility="@{ConverterUtil.isZero(viewmodel.likes)}"` 27 | * 28 | * This converts "likes" (an Int) into a Boolean. See [BindingConverters] for the conversion 29 | * from Boolean to a visibility integer. 30 | */ 31 | object ConverterUtil { 32 | @JvmStatic fun isZero(number: Int): Boolean { 33 | return number == 0 34 | } 35 | } 36 | 37 | /** 38 | * The number of likes is an integer and the visibility attribute takes an integer 39 | * (VISIBLE, GONE and INVISIBLE are 0, 4 and 8 respectively), so we use this converter. 40 | * 41 | * There is no need to specify that this converter should be used. [BindingConversion]s are 42 | * applied automatically. 43 | */ 44 | object BindingConverters{ 45 | 46 | @BindingConversion 47 | @JvmStatic fun booleanToVisibility(isNotVisible: Boolean): Int { 48 | return if (isNotVisible) View.GONE else View.VISIBLE 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/util/BindingMethods.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.util 18 | 19 | import androidx.databinding.BindingMethod 20 | import androidx.databinding.BindingMethods 21 | import android.widget.ImageView 22 | 23 | 24 | /** 25 | * `app:srcCompat` is an attribute used by the support library to integrate vector drawables. This 26 | * BindingMethod binds the attribute to the setImageDrawable method in the ImageView class. 27 | * 28 | * Binding methods have to be applied to any class in your project. Even an empty one. 29 | * 30 | * This is equivalent to: 31 | * ``` 32 | * 33 | * @BindingAdapter("app:srcCompat") 34 | * @JvmStatic fun srcCompat(view: ImageView, @DrawableRes drawableId: Int) { 35 | * view.setImageResource(drawable) 36 | * } 37 | * ``` 38 | */ 39 | @BindingMethods( 40 | BindingMethod(type = ImageView::class, 41 | attribute = "app:srcCompat", 42 | method = "setImageResource")) 43 | class MyBindingMethods 44 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/databinding/basicsample/util/ObservableViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.databinding.basicsample.util 18 | 19 | import androidx.lifecycle.ViewModel 20 | import androidx.databinding.Bindable 21 | import androidx.databinding.Observable 22 | import androidx.databinding.PropertyChangeRegistry 23 | 24 | 25 | /** 26 | * A ViewModel that is also an Observable, to be used with Data Binding. 27 | */ 28 | open class ObservableViewModel : ViewModel(), Observable { 29 | 30 | private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry() 31 | 32 | override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) { 33 | callbacks.add(callback) 34 | } 35 | 36 | override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) { 37 | callbacks.remove(callback) 38 | } 39 | 40 | /** 41 | * Notifies listeners that all properties of this instance have changed. 42 | */ 43 | fun notifyChange() { 44 | callbacks.notifyCallbacks(this, 0, null) 45 | } 46 | 47 | /** 48 | * Notifies listeners that a specific property has changed. The getter for the property 49 | * that changes should be marked with [Bindable] to generate a field in 50 | * `BR` to be used as `fieldId`. 51 | * 52 | * @param fieldId The generated BR id for the Bindable field. 53 | */ 54 | fun notifyPropertyChanged(fieldId: Int) { 55 | callbacks.notifyCallbacks(this, fieldId, null) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 28 | 29 | 35 | 38 | 41 | 42 | 43 | 44 | 50 | 51 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 24 | 27 | 32 | 37 | 42 | 47 | 52 | 57 | 62 | 67 | 72 | 77 | 82 | 87 | 92 | 97 | 102 | 107 | 112 | 117 | 122 | 127 | 132 | 137 | 142 | 147 | 152 | 157 | 162 | 167 | 172 | 177 | 182 | 187 | 188 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable/ic_person_black_96dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable/ic_whatshot_black_96dp.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 23 | 24 | 28 | 29 |