in databinding of android simply i want to control view visibility by checking viewmodel parameter as profilePicUrl
Solution 1:
when profilePicUrl is empty view should be gone, otherwise that should be visible, for example:
<data>
<import type="android.view.View"/>
<import type="android.text.TextUtils"/>
<variable name="viewModel" type="xx.xxxxx.xxxxxxx.MyViewModel"/>
</data>
...
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{TextUtils.isEmpty(viewModel.profilePicUrl) ? View.GONE : View.VISIBLE}"
>
i get this error:
error: '#{TextUtils.isEmpty(viewModel.profilePicUrl)? View.GONE : View.VISIBLE' is incompatible with attribute visibility (attr) enum [gone=2, invisible=1, visible=0].
Solution 2:
after getting this error i try to test another solution to approach that, for example:
BindingAdapters class:
object BindingAdapters {
#BindingAdapter("visibleIf")
#JvmStatic
fun changeVisibility(#NonNull imageView: ImageView, visible: Boolean) {
if (visible) {
imageView.visibility = View.VISIBLE
} else {
imageView.visibility = View.GONE
}
}
}
xml layout:
<data>
<import type="android.view.View"/>
<import type="android.text.TextUtils"/>
<variable name="viewModel" type="xx.xxxxx.xxxxxxx.MyViewModel"/>
</data>
...
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:visibleIf="#{TextUtils.isEmpty(viewModel.profilePicUrl)}"
>
i get this error:
error: attribute visibleIf (aka xxx.xxxxx.xxxxxxxx:visibleIf) not found.
Are you sure you have enabled data-binding properly? Your code is correct, so there is no reason why it shouldn't work.
Please make sure this code is added in the Gradle file of your app module:
dataBinding {
enabled = true
}
In addition, I would recommend adding this to the gradle.properties file:
android.databinding.enableV2=true
Here you can see a similar question.
Regarding the second solution, sometimes importing the object that contains the adapter methods helps:
<import type="com.your.package.name.BindingAdapters" />
Also, please make sure you have added this attribute on your layout tag:
xmlns:app="http://schemas.android.com/apk/res-auto"
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'm currently using bindings to dynamically set the texts of various text views using the android view models. At the moment the view models look something like this:
class MyViewModel(
resources: Resources,
remoteClientModel: Model = Model()
) : ObservableViewModel() {
init {
observe(remoteClientModel.liveData) {
notifyChange()
}
fun getTextViewTitle(): String = when {
someComplicatedExpression -> resources.getString(R.string.some_string, null)
else -> resources.getString(R.string.some_other_string)
}
}
And the xml layout:
<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="my.app.signature.MyViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{viewModel.textViewTitle}"
android:textAlignment="center"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
However I would like to remove the "resources: Resources" that is injected into the view model, since the resources are coupled with the Activity. The code now simply returns the string resource id instead:
fun getTextViewTitle(): Int = when {
someComplicatedExpression -> R.string.some_string
else -> R.string.some_other_string
}
Hence I've removed the activity dependency. The compiler thinks this is fine but it crashes in runtime with the following exception: android.content.res.Resources$NotFoundException: String resource ID #0x0.
This happens when trying to attach the lifeCycleOwner to the binding using:
override fun onActivityCreated(savedInstanceState: Bundle?) {
// Some more code....
binding.lifecycleOwner = activity
// Some more code....
I'm not sure how to remove the resource dependency from the view model without having it crash in runtime.
EDIT:
For clarification: The ObservableViewModel in my example is the very same one as the one found here:
https://developer.android.com/topic/libraries/data-binding/architecture
Used to perform notifyChange.
The issue here is the code is trying to call textView.setText(0) which results in an error since there is no string resource with id 0x0. This is happening because getTextViewTitle() return an Int and the view binding functionality will make it default as 0 (when initializing).
https://developer.android.com/topic/libraries/data-binding/expressions#property_reference
From the docs
Avoiding null pointer exceptions
Generated data binding code automatically checks for null values and avoid null pointer exceptions. For example, in the expression #{user.name}, if user is null, user.name is assigned its default value of null. If you reference user.age, where age is of type int, then data binding uses the default value of 0.
Maybe something like this could work,
android:text='#{viewModel.textViewTitle == 0 ? "" : #string{viewModel.textViewTitle}}'
or
android:text='#{viewModel.textViewTitle, default=""}'
To solve this simply make a context available in the view, so that you can call context.getString(...) in your view.
<data>
<import type="androidx.core.content.ContextCompat" />
<variable
name="viewModel"
type="my.application.path.SomeViewModel" />
</data>
<....
....
android:text="#{context.getString(viewModel.textResource)}"
...
/>
Just convert your int value to String to avoid this crush
android:text='#{String.valueOf(viewModel.profile.walletBalance)}'
In some cases your binding variable itself can be null
<data>
<variable
name="viewModel"
type="SomeViewModel"
/>
</data>
<TextView
android:text="#{viewModel == null ? "" : viewModel.textViewTitle}"
/>
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'm trying to assign a behavior different than the standard one when my swipelayout is refreshed:
This is my code:
binding.refreshed = binding.refreshOtrosRequestList
binding.refreshOtrosRequestList!!.setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener {
[Change behavior]
})
These are the elements I'm using defined in the corresponding XML file:
<data>
<variable name="refreshed" type="androidx.swiperefreshlayout.widget.SwipeRefreshLayout"/>
</data>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/refreshOtrosRequestList"
app:onRefreshListener="#{() -> model.onRefresh(refreshed)}"
android:layout_width="wrap_content"
android:layout_height="match_parent">
But the defined setOnRefreshListener never fires:
What am I doing wrong?
PD: this is request, also in data in XML file:
<data>
<variable name="model" type="es.nscontrol.controlpresencial.viewmodels.NonWorkingDateViewModel"/>
<variable name="refreshed" type="androidx.swiperefreshlayout.widget.SwipeRefreshLayout"/>
</data>
What is "model"?
model.onRefresh(refreshed)
Is it somewhere defined?
Additionally, it looks strange when you use databiding to store view from this layout in a variable.
Edit:
I hope you also binded this "model" to the actual variable as you did with
binding.refreshed = binding.refreshOtrosRequestList
And invoke this after you binded these variables:
binding.executePendingBindings()
I'm trying to set an OnClickListener for an <include>d layout, but receive a data binding error at compile time stating that data binding "Cannot find the setter for attribute 'android:onClick' with parameter type android.view.View.OnClickListener".
Context here is that I'm using data binding to inflate the included layout, so that I can pass values into it from a viewModel that I've bound to the including layout.
I've tried various syntax for the data binding expression:
#{viewModel::onClickFunction}
#{viewModel.onClickFunction}
#{() -> viewModel.onClickFunction()}
#{(view) -> viewModel.onClickFunction()}
#{_ -> viewModel.onClickFunction()}
I've tried all of the above with onClickFunction as a function, and also as an OnClickListener object.
Other related questions on Stack Overflow seem to solve this issue by cleaning the project to regenerate the databinding files, but that hasn't worked for me.
Relevant code snippets below:
viewModel
class MyViewModel() {
val onClickFunctionAsAListener: OnClickListener = object:OnClickListener{
override fun onClick(v: View?) {
//Do something
}
}
fun onClickFunction() {
//Do something
}
}
Including layout
<layout>
<data>
<variable name="viewModel" type="full.package.name.MyViewModel"/>
</data>
<LinearLayout>
<include
layout="#layout/included_layout"
android:onClick="#{viewModel.onClickListener}"
app:customAttribute="#{`someText`}/>
</LinearLayout>
</layout>
Included layout
<layout>
<data>
<variable name="customAttribute" type="String"/>
</data>
<TextView
layout="#layout/included_layout"
android:text="#{customAttribute}"/>
</layout>
It seems that you can't actually assign an OnClick handler to an <include> tag directly. I managed to get it to work by adding another variable to IncludedLayouts data binding class, and then assigning the OnClickListener to IncudedLayouts root view in XML.
After the changes, my files looked like this:
viewModel
class MyViewModel() {
val onClickFunction: OnClickListener = object:OnClickListener{
override fun onClick(v: View?) {
//Do something
}
}
}
Including layout
<layout>
<data>
<variable name="viewModel" type="full.package.name.MyViewModel"/>
</data>
<LinearLayout>
<include
layout="#layout/included_layout"
app:onClick="#{viewModel.onClickListener}"
app:customAttribute="#{`someText`}/>
</LinearLayout>
</layout>
Included layout
<layout>
<data>
<variable name="customAttribute" type="String"/>
<variable name="onClick" type="android.view.View.OnClickListener"/>
</data>
<TextView
layout="#layout/included_layout"
android:text="#{customAttribute}"
android:onClick="#{onClick}"/>
</layout>
include tag does not support onClick method directly. While the selected answer is correct, instead of passing onClickLister to include layout (or having custom #BindingAdapter, which is also another solution), I would just wrap my include inside a ViewGroup and onClick on ViewGroup.
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{()-> viewModel.yourFunction()}">
<include layout="#layout/custom_layout"/>
It's a workaround, but works as charm.