I have been trying to use BindingAdapter in Android Studio 3.4 (the last update) with Kotlin for days now and nothing seems to work.
I first tried with the following tutorial: https://codelabs.developers.google.com/codelabs/android-databinding/#7
And it was outputing an error as soon as I reached the 8th step.
Furthermore I tried the simple example possible with an Empty Application, a single Activity, a single ViewModel, and a single BindingAdapter. Here is the XML code.
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="viewmodel"
type="com.example.testbindingadapter.DataViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:greetings="#{viewmodel.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="#+id/textView"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Now here is the ViewModel with the BindingAdapter
class DataViewModel : ViewModel() {
private val _name = MutableLiveData<String>()
val name : LiveData<String> = _name
init {
_name.value = "Amath"
}
}
#BindingAdapter("greetings")
fun setName(view: TextView, text: String) {
view.text = "Welcome, $text"
}
I have also enabled dataBinging in my Graddle. I added apply plugin: 'kotlin-kapt' as suggested in the following thread Cannot find the setter for attribute in Data binding. At first I had an error msg:Cannot find the setter for attribute databinding subsequently the error disappeared, but the app simply crashed.
Can you help ?
You never set the viewmodel into databinding:
binding.viewmodel = viewModel
Related
I want to start using viewBinding in our project but the mere addition of the configuration results in a compile error:
android {
buildFeatures {
dataBinding true
viewBinding true // new line and only change
}
results in:
e: /home/leo/StudioProjects/android-wallet/mbw/build/generated/source/kapt/btctestnetDebug/com/mycelium/wallet/DataBinderMapperImpl.java:37: error: cannot find symbol
import com.mycelium.wallet.databinding.FragmentBequantAccountBindingImpl;
^
symbol: class FragmentBequantAccountBindingImpl
location: package com.mycelium.wallet.databinding
Cannot find a setter for <com.mycelium.wallet.databinding.ItemBequantSearchBinding app:visibility> that accepts parameter type 'int'
If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.
The offending code is:
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.mycelium.bequant.market.viewmodel.AccountViewModel" />
</data>
...
<include
android:id="#+id/searchBar"
layout="#layout/item_bequant_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{viewModel.searchMode ? View.VISIBLE : View.GONE}" removing="this line fixes compilation"
app:layout_constraintTop_toBottomOf="#id/hideZeroBalance" />
Changing the offending line to any of
android:visibility="#{viewModel.searchMode ? `visible` : `gone`}"
app:visibility="#{viewModel.searchMode ? View.VISIBLE : View.GONE}"
results in similar errors.
I read I might have to define a BindingAdapter but why and where?
I tried adding
#BindingAdapter("visibility")
fun setVisibility(target: View, visible: Boolean) {
target.visibility = if (visible) View.VISIBLE else View.GONE
}
to AccountFragment which inflates above xml file changing the xml to
android:visibility="#{viewModel.searchMode}"
but this appears to have no effect.
Both fragment_bequant_account.xml and item_bequant_search.xml use androidx.constraintlayout.widget.ConstraintLayout instead of androidx.constraintlayout.ConstraintLayout.
I tried to put a #BindingAdapter into the AccountViewModel as suggested here but with no success.
I had the same problem in my project. I used databinding in my code and had dataBinding true in the gradle. As soon as I added viewBinding true I got the same error pointing to the xml line android:visibility="#{viewModel.searchMode ? View.VISIBLE : View.GONE}"
To fix, I added the tools:viewBindingIgnore="true" attribute to the root view of a certain layout file so that layout is ignored while generating binding classes.
You can see documentation on the tools:viewBindingIgnore="true" attribute at https://developer.android.com/topic/libraries/view-binding#data-binding
The problem is in the viewBinding trying to create the binding class of the layout in the include.
It seems that the binding class created for the main layout(dataBinding) manages the included layout in a different way when viewBinding = true and don't understand it's attrs
As James said tools:viewBindingIgnore="true" is the solution, in this case it must be in the included layout(layout="#layout/item_bequant_search").
Every reused layout must have tools:viewBindingIgnore="true" to avoid this issues
The problem is with this statement
app:visibility="#{viewModel.searchMode ? View.VISIBLE : View.GONE}"
it evaluates and pass View.VISIBLE or View.GONE to the binding adapter method,But
#BindingAdapter("visibility")
fun setVisibility(target: View, visible: Boolean)
As your method signature says it expects a boolen but evaluation results in int i.e. either View.VISIBLE or View.GONE.
The issue can be solved by removing the evaluation and passing the boolean directly.
app:visibility="#{viewModel.searchMode}"
I assument viewModel.searchMode is a boolean variable.
Lets you create a kotlin file Named BindingAdapters.kt
Paste this method directly there
#BindingAdapter("visibility")
fun setVisibility(target: View, visible: Boolean) {
target.visibility = if (visible) View.VISIBLE else View.GONE
}
else lets say you have a class BindingAdapters in a file BindingAdapters.kt
class BindingAdapters{
companion object{
#BindingAdapter("visibility")
#JvmStatic// it is important
fun setVisibility(target: View, visible: Boolean) {
target.visibility = if (visible) View.VISIBLE else View.GONE
}
}
}
I got similar error and this is my solution:
You only add tag '<layout.../layout>' to all include layout like this:
In main layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewId"
type="Integer" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/bg">
<include
android:id="#+id/img_no_data"
layout="#layout/layout_no_data"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="#{viewId==0? View.VISIBLE: View.GONE}"
app:layout_constraintBottom_toTopOf="#+id/btn_camera"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
In include layout: add tag '<layout...' too:
<layout>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageView2"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="#dimen/_16sdp"
android:layout_marginEnd="#dimen/_16sdp"
android:src="#drawable/bg_no_data"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="986:817"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Done! Hope this help you.
I have made a binding adapter available statically inside my Fragment which basically change my button appearance from "Stop" to "Play" and vice-versa.
companion object {
#BindingAdapter("playState")
fun Button.setPlayState(item: UIState) {
item.let {
if (it.isPlaying) {
setText("Stop")
setBackgroundColor(ContextCompat.getColor(context, R.color.colorStop))
} else {
setText("Play")
setBackgroundColor(ContextCompat.getColor(context, R.color.colorPlay))
}
}
}
}
Here is my layout file. I have provided a data class for it.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<!-- stuff here -->
<variable
name="viewmodel"
type="com.mypackage.ui.ViewModel"/>
<variable
name="uistate"
type="com.mypackage.ui.UIState" />
</data>
<!-- layout, buttons, and more stuff here. Just pay attention to this following button -->
<Button
android:id="#+id/play_button"
android:layout_width="150sp"
android:layout_height="75sp"
android:layout_marginTop="20sp"
android:onClick="#{() -> viewmodel.onPlayClicked()}"
android:text="#string/play_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/minus_layout"
app:layout_constraintVertical_bias="0.026"
app:playState="#{uistate}"/>
</layout>
UIState itself is pretty self-explanatory.
data class UIState(var isPlaying: Boolean)
and the () -> viewmodel.onPlayClicked() flips the Boolean at UIState.
After compiling, Data Binding Compiler throws this error:
Cannot find a setter for <android.widget.Button app:playState>
that accepts parameter type 'com.mypackage.ui.UIState'
I have tried:
Rebuilding the project by removing .gradle folder
Looking for answer here and here.
Removed #JvmStatic annotation at the extension function
Moved the extension function to top level instead of Fragment's companion object.
I think you missed to add kotlin plugin in your gradle
apply plugin: 'kotlin-kapt'
You don't have to use #JvmStatic because you are using Kotlin extension feature.
You need to add the view reference as a paramater to your BindingAdapter method.
#BindingAdapter("playState")
fun setPlayState(button:Button,item: UIState) {
//do your work here
}
Your namespace
xmlns:app="http://schemas.android.com/apk/res-auto"
is wrong for custom binding adapters. Please use the namespace
xmlns:app="http://schemas.android.com/tools"
since app:playState is not in the namespace you have given its not working properly
I have a loginViewModel for my login Activity, in activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="loginViewModel"
type="com.example.test.ui.login.LoginViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/login_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="#+id/mobile_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:editable="true"
android:ems="10"
android:hint="#string/mobile_number_string"
android:inputType="phone"
android:textAlignment="center"
android:text="#={loginViewModel.phoneNumber}"
app:layout_constraintBottom_toTopOf="#+id/otp_input"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And in my loginViewModel i have defined my livedata as
class LoginViewModel : ViewModel(){
private val _phoneNumber = MutableLiveData<String>()
val phoneNumber : LiveData<String>
get() = _phoneNumber
}
Now, while building I am getting the following error
The expression \u0027loginViewModelPhoneNumber.getValue()\u0027 cannot be inverted, so it cannot be used in a two-way binding\n\nDetails: There is no inverse for method getValue, you must add an #InverseMethod annotation to the method to indicate which method should be used when using it in two-way binding expressions
All articles that I am reading suggesting this way to implement.
Can someone tell me what am I doing wrong here?
Unfortunately for two-way data binding you need to use MutableLiveData.
You should remove private on _phoneNumber.
Then change xml to use it android:text="#={loginViewModel._phoneNumber}".
change from
private val _phoneNumber = MutableLiveData<String>()
to
public val _phoneNumber = MutableLiveData<String>()
You are binding phoneNumber which is a LiveData that does not have any interface for writing value.
Consider removing phoneNumber, and using Kotlin-based approach, with just a public property
There are several issues with the code.
you try to use val phoneNumber that is not mutable in two way binding, thus is has only getter so binding class can only read value from field, to receive data from the UI - binding class wants to use setter, but there is no setter present, thus the phoneNumber won't change.
for the property that binds phoneNumber you try to use LiveData which is not mutable - you should use MutableLiveData in case you need it to be able to change.
if you want to listen to the changes of phoneNumber you need to add LiveData Observer like
phoneNumber.observe{
val value = it
}
Hope it helps.
data binding error ****msg:Cannot find the getter for attribute 'android:checked' with value type java.lang.Boolean on android.widget.CheckedTextView.
I have a Kotlin Android app and one of the XML layouts contains a CheckedTextView and I want to two-way bind the checked property to the checked value of the ViewModel. The idea is that the checked property in the viewModel will represent the one on the view itself. This fails with the error message above. Now I wonder whether this is because checked is a boolean value and the getter is called isChecked. Can Databinding not recognize that? So I tried extending it with a getChecked function, but that didn't resolve the error. Maybe because while Kotlin supports extension functions, Java does not. Any ideas how this can be solved?
XML file:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="viewModel"
type="lehrbaum.de.onenightcomps.view.SimpleCheckableListItemViewModel"/>
</data>
<CheckedTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/textView"
android:padding="#dimen/text_margin"
android:gravity="center_vertical"
android:textStyle="bold"
android:checkMark="?android:attr/listChoiceIndicatorSingle"
android:checkMarkTint="#color/colorPrimary"
android:checked="#={viewModel.checked}"
android:text="#{viewModel.text}"/>
</layout>
ViewModel class:
class SimpleCheckableListItemViewModel {
val checked : MutableLiveData<Boolean> = MutableLiveData()
val text : MutableLiveData<String> = MutableLiveData()
}
Extension function:
fun CheckedTextView.getChecked(): Boolean {
return this.isChecked
}
There might be different reasons for this error but in my case, the problem raised up because I didn't add apply plugin: 'kotlin-kapt' And apply plugin: 'kotlin-android-extensions' in my Gradle.
After adding these plugins you have to replaced your annotationProcessors with kapt.
After that, every thing might be going well.
I am experimenting the new architecture components from Google trying to achieve more reactive code using ViewModel, LiveData and DataBinding.
Basically my idea around ViewModel is to have only one field of type Model(user for the record since we are representing a user profile scree). So my ViewModel class is :
class ViewModel : ViewModel() {
var model = MutableLiveData<User>()
and my Model class is :
class User(var name: String, var lastName: String, var age: Int)
In my layout file, I am trying to bind the fields on my Model into the view using DataBinding plugin. The problem is that since my ViewModel has a MutableLiveData<User> I can't access (from xml binding) the fields inside the User class (name, lastName...).
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.github.andromedcodes.mvvmtutorial.ViewModel" />
</data>
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/text_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="#{viewModel.user.name}"/>
</RelativeLayout>
</layout>
Is it even possible to do that? And which is better, having a ViewModel with separate Fields (String, Int, Whatever...) or re-using a Model?
You can see my repository where I did everything using DataBinding and MVVM pattern . Just visit https://github.com/xyarim/android-architecture