How to pass null argument in Android Databinding - android-databinding

Why can't pass null value? how can fix it? I can't find any hint from Document.
ERROR
****/ data binding error ****msg:cannot find method onClick(java.lang.Object, java.lang.Object) in class kr.co.app.MyActivity.MyListener file:/Users/jujaeho/projects/app/src/main/res/layout/activity_my.xml loc:24:71 - 24:106 ****\ data binding error ****
CODE
class MyActivity {
interface MyListener {
fun onClick(abc: ABC?, count: Int?)
}
}
<layout>
<data>
<variable
name="handler"
type="kr.co.app.MyActivity.MyListener" />
</data>
<View
...
android:onClick="#{() -> handler.onClick(null, null)}" />
</layout>

I just encountered this problem today, and what I did basically was to cast the null parameters to the types expected in the method parameters. In your case, this should be something like:
<layout>
<data>
<import type="ABC" /> // just an illustration, specify the full package
<variable
name="handler"
type="kr.co.app.MyActivity.MyListener" />
</data>
<View
...
android:onClick="#{() -> handler.onClick((ABC) null, (int) null)}" />
</layout>
I am not sure about the int casting, but you can try it or use the Integer wrapper class for casting.

if you are intending to pass null why cannot you assign default value for your onClick method
class MyActivity {
interface MyListener {
fun onClick(abc: ABC?=null, count: Int?=null)
}
}
you can pass nothing if you want to pass null

Related

Need MotionLayout Reference using DataBinding Tree

I'm trying to get the reference to my MotionLayout inside a Fragment inside my RecyclerView so that I can attach my addTransitionListener. I've got the listener prepared, but I don't know the valid path to this object.
I've seen many great examples that reference motionLayout vars, but I can't seem to get a valid one using DataBinding methods.
My RecyclerView is inside my Fragment as:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/shipper_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:background="#color/mainList"
tools:listitem="#layout/row_shipper_line"
/>
My MotionLayout is inside my "row_shipper_line" as:
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="#+id/line_container"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#color/mainList"
app:layoutDescription="#xml/row_shipper_line_container_scene"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
My binding var is set as:
binding = DataBindingUtil.inflate(
inflater,
R.layout.create_shipper_fragment,
container,
false
)
I've got this code entered and it compiles, but the events aren't called:
rec = LayoutInflater.from(context).inflate(R.layout.shipper_list, null)
ml = rec.findViewById(R.id.line_container)
var transitionListener = object : MotionLayout.TransitionListener {
override fun onTransitionStarted(p0: MotionLayout?, startId: Int, endId: Int) {
Log.d("MyApp", "Transition onTransitionStarted.")
}
override fun onTransitionChange(
p0: MotionLayout?,
startId: Int,
endId: Int,
progress: Float
) {
Log.d("MyApp", "Transition onTransitionChange.")
}
override fun onTransitionCompleted(p0: MotionLayout?, currentId: Int) {
Log.d("MyApp", "Transition onTransitionCompleted.")
}
override fun onTransitionTrigger(
p0: MotionLayout?,
triggerId: Int,
positive: Boolean,
progress: Float
) {
Log.d("MyApp", "Transition onTransitionTrigger.")
}
}
ml.setTransitionListener(transitionListener)
My line swipes LEFT perfectly. I just need to listen for onTransitionComplete.
My binding tree looks like this:
Fragment containing my RecyclerView -> Row Layout containing the MotionLayout tag.
What I'm trying to do is something like this:
binding.shipper_list.line_container
Thank you in advance!

Why my data-binding annotation(#BindingAdapter("imageURL")) doesn't work?

I've written my custom recyclerView and have got a problem. But when I wrote an annotation to mark a setter for my imageView I got a compilation error.
Here are the source code and errors.
Error:
Cannot find a setter for <com.makeramen.roundedimageview.RoundedImageView app:imageURL> that accepts parameter type 'java.lang.String'
If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.
Setter code:
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import java.lang.Exception
class BindingAdapters {
#BindingAdapter("imageURL")//compilation error
fun setImageURL(imageView: ImageView, URL: String?) {
imageView.alpha = 0f
try {
Picasso.get().load(URL).noFade().into(imageView, object : Callback {
override fun onSuccess() {
imageView.animate().setDuration(300).alpha(1f).start()
}
override fun onError(e: Exception) {
}
})
} catch (ignored: Exception) {
}
}
}
ImageView xml code:
<data>
<variable
name="eventShow"
type="course.ru.qsearcher.models.Event" />
</data>
........
<com.makeramen.roundedimageview.RoundedImageView
android:id="#+id/imageEvent"
android:layout_width="#dimen/_70sdp"
android:layout_height="#dimen/_100sdp"
android:layout_marginStart="#dimen/_10sdp"
app:imageURL="#{eventShow.imagePath}"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:riv_corner_radius="#dimen/_4sdp" />
Event is a data class with some fields like the title of an event, description, image URL, and so on.
сode
xml usage
url-field
Change android:imageURL to imageURL, the android: prefix is for built-in attribute like android:text.
And use it like imageURL or app:imageURL in your ImageView.
I didn't add the Kotlin plugin to my gradle file
apply plugin: "kotlin-kapt"
That solved my problem

How to debug myBatis NoSuchMethodException in kotlin?

I have a simple class like this:
data class Foo(
val id: Long,
val created: LocalDateTime,
val myBool: Boolean? = null,
val comment: SensitiveString? = null
)
I get the exception:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Error instantiating class com.XXX.XXX.XXX.XXX.Foo with invalid types (long,LocalDateTime,boolean,SensitiveString) or values (XX,2020-03-19T17:36:30.415,false,0#SensitiveString).
Cause: java.lang.NoSuchMethodException: com.XXX.XXX.XXX.XXX.Foo.<init>(long, java.time.LocalDateTime, boolean, com.XXX.XXX.XXX.XXX.SensitiveString)
Here is my xml resultMap:
<resultMap id="fooMap" type="com.XXX.XXX.XXX.XXX.Foo">
<constructor>
<arg column="id" javaType="_long"/>
<arg column="created" javaType="java.time.LocalDateTime"
typeHandler="com.XXX.XXX.XXX.XXX.LocalDateTimeTypeHandler"/>
<arg column="myBool" javaType="_Boolean"/>
<arg column="comment" javaType="com.XXX.XXX.XXX.XXX.SensitiveString"
typeHandler="com.XXX.XXX.XXX.XXX.EncryptedStringTypeHandler"/>
</constructor>
</resultMap>
How can I solve this? the asked constructor should definitely be available, so it is very confusing.
Moreover: when I try to create my own constructor with all the fields then kotlin complains saying the constructor is conflicting with the implicit one.
How can I tackle this?
I had this exact problem. The issue for me was the Boolean? type - the Java type is java.lang.Boolean when the type is nullable. I did it with annotations, so it would look like this:
#ConstructorArgs (
...
Arg(column = "myBool", javaType = java.lang.Boolean::class)
...
)

How to get a RecyclerView Item to update itself from a LiveData value in it's binding object using BindingAdapter?

I have a recyclerview.widget.ListAdapter to display some cards in a grid. When I click on a card, it comes to the front of the grid, flips, and when you see the front, you are supposed to be able to like it. I have an ImageView on the corner of the front of the card, that is supposed to toggle between liking or not, displaying a different drawable that I have already defined.
My ViewHolder looks like this:
class GameCardViewHolder(private val binding : ItemGameCardBinding) :
RecyclerView.ViewHolder(binding.root){
companion object {
fun from(parent: ViewGroup): GameCardViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemGameCardBinding.inflate(layoutInflater, parent, false)
return GameCardViewHolder(binding)
}
...
}
fun bind(productCard: ProductCard, listener: OnClickListener){
binding.productCard = productCard
binding.executePendingBindings()
...
}
}
My item_game_card.xml looks like this:
<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="productCard"
type="company.app.model.ProductCard" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout>
...
<ImageView
android:id="#+id/gameItemLikeImageView"
android:layout_width="0dp"
android:layout_height="0dp"
...
app:imageDrawable="#{productCard.liked}"
android:onClick="#{() -> productCard.toggleLike()}"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
My BindingAdapters.kt:
#BindingAdapter("imageDrawable")
fun bindImageDrawable(imgView: ImageView, boolean: LiveData<Boolean>?) {
Timber.d("Binding liked: $boolean")
when(boolean?.value){
true -> imgView.setImageResource(R.drawable.ic_card_like)
false -> imgView.setImageResource(R.drawable.ic_card_dislike)
}
}
And my ProductCard.kt:
data class ProductCard(
val position: Int,
val product: Product
){
...
private val _liked = MutableLiveData(false)
val liked : LiveData<Boolean>
get() = _liked
fun toggleLikeProduct(){
Timber.d("Toggle like before: ${_liked.value}")
val oldValue = _liked.value!!
_liked.value = !oldValue
Timber.d("Toggle like after: ${_liked.value}")
}
}
When I click on the ImageView, the console prints out the before and after values every time correctly, so I know the value of the liked LiveData is actually changing, but the View is not updating accordingly, that is because the binding adapter is only being called once (in the binding.executePendingBindings() I guess, when the RecyclerView binds the ViewHolder, and not every single time the LiveData changes).
I want the BindingAdapter code to be run every time the liked LiveData changes.
When I do this with ViewModel and Fragment layout, every time a LiveData changes in the ViewModel, the View changes because the xml is referencing that LiveData, but I don't know why, when it comes to ProductCard and recyclerview item layout, even if the xml is referencing that LiveData, the View doesn't change, the xml is not actually binding to the LiveData, which is the whole point of having LiveData.
I have already acheived this behaviour with a very brute, unelegant sort of way, setting an onClickListener on that view, and forcing it to change it's resource drawable, on the OnBindViewHolder like this:
fun bind(productCard: ProductCard, listener: OnClickListener){
binding.productCard = productCard
binding.executePendingBindings()
binding.gameItemLikeImageView.setOnClickListener {
it?.let {
val card = binding.productCard
Timber.d("Tried changing liked status: ${card!!.liked}")
val old = card.liked
card.liked = !old
binding.productCard = card
val card2 = binding.productCard
Timber.d("Tried changing liked status: ${card2!!.liked}")
if (!old){
it.setBackgroundResource(R.drawable.ic_card_like)
}
else {
it.setBackgroundResource(R.drawable.ic_card_dislike)
}
}
}
...
}
But I would really like that the behaviour of each card be controlled by the state of the ProductCard object that it is bind to.
I have also tried to force call binding.executePendingBindings() every time I click, but also didn't work.
How can I make the item_game_card.xml to observe the changes in the LiveData?
app:imageDrawable won't accept any LiveData<Boolean> and I'd wonder why it isn't LiveData<Product>? Try to bind the initial value alike this:
app:imageDrawable="#{product.liked ? R.drawable_liked : R.drawable_neutral}"
For LiveData the LifecycleOwner needs to be set for the binding:
class ProductsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Inflate view and obtain an instance of the binding class.
val binding: ProductsBinding = DataBindingUtil.setContentView(this, R.layout.products)
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this)
}
}
see the documentation.

Kotlin compiler: Data binding error, cannot find method

Mirgating from Java to Kotlin I try to use static function with Data Binding:
<data>
<import type="com.package.domain.tools.helper.StringValidator"/>
...
</data>
Then I call function hideNumber:
<com.hastee.pay.ui.view.Text
...
android:text='#{StringValidator.hideNumber(account.number)}'
app:layout_constraintRight_toRightOf="#+id/number"
app:layout_constraintTop_toBottomOf="#+id/number" />
Using databinding here causes error:
[kapt] An exception occurred:
android.databinding.tool.util.LoggedErrorException: Found data binding
errors.
****/ data binding error ****msg:cannot find method
hideNumber(java.lang.String) in class
com.package.domain.tools.helper.StringValidator....
Here's this object:
object StringValidator {
...
fun hideNumber(number: String): String {
return "****" + number.substring(number.length - 4)
}
}
How can I reach this function using Kotlin and Data Binding?
The data binding compiler is looking for a static method.
Since a named object alone is not enough to make all methods inside that object static, you need an additional #JvmStatic annotation on your hideNumber-method:
#JvmStatic
fun hideNumber(number: String): String {
return "****" + number.substring(number.length - 4)
}
see also: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods