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
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 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
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.
Via data-binding I am setting the visibility of a text field. The visibility is depending on a string being null or empty or nothing of the both.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="viewModel"
type="com.example.viewModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
<TextView
android:id="#+id/textField1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.data.text}"
android:visibility="#{(viewModel.data.text == null || viewModel.data.text.empty) ? View.GONE : View.VISIBLE}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
Is it possible to create an import in the data element so that I can use the isNullOrBlank() function from the kotlin.text.Strings.kt class?
I was hoping to be able to use it like this: android:visibility="#{(viewModel.data.title.isNullOrBlank() ? View.GONE : View.VISIBLE}"
Android data binding still generates the Java code from the XML instead of the Kotlin code, once data binding will be migrated to generating Kotlin code instead of Java I believe we will be able to use Kotlin extension function in the XML, which will be really cool.
I am sure that gonna happen real soon as Google is pushing to Kotlin heavily. But for now, you have below
TextUtils.isEmpty() as mentioned by #Uli Don't forget to write an import.
The reason why you can't you use StringKt.isNullOrBlack in xml:
Below is the code from Kotlin String.kt
#kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this#isNullOrEmpty != null)
}
return this == null || this.length == 0
}
As you can see it is annotated with #kotlin.internal.InlineOnly which says the Java generated code of this method will be private.
InlineOnly means that the Java method corresponding to this Kotlin
function is marked private so that Java code cannot access it (which
is the only way to call an inline function without actual inlining
it).
It means it can't be called from Java and as data binding generated code is in JAVA it can't be used in data binding either. Thumb rule is what you can access from JAVA you can use that in data binding if not just use the old Java way I would say.
TextUtils.isEmpty() should do what you want.
What is the type of the Observable class property which getter is annotated as #Bindable in the Android Data Binding framework?
For example, let the Observable class be defined as follows:
class Localization() : BaseObservable() {
var translation: (key: String) -> String by Delegates.observable(defaultTranslation) { _, _, _ ->
notifyPropertyChanged(BR.translation)
}
#Bindable get
}
The layout XML will be then something like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="translation"
type="WHAT IS THE TYPE OF TRANSLATION?" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{translation.invoke(stringKey)}" />
</FrameLayout>
</layout>
The question is, what to put in the type attribute of variable "translation".
I've tried:
type="kotlin.jvm.functions.Function1<String, String>"
It compiles, but the TextView is not updated when translation property changes.
I can achieve the desired behavior by introducing localization variable in the layout XML and then calling localization.translation.invoke() in the binding expression. I am just not comfortable with this and want to know if I can reference translation directly.
The Localization extends BaseObservable while Function1 is not observable at all. So using the Localization gives you an interface for observing the changes to the properties.
If you bind the translation, it's a simple field that gets set. If you want to update it, you'd have to call setTranslation() again.