Search code examples
androidandroid-recyclerviewandroid-databinding

Android data binding doesn't generate a bounded view in RecyclerView


I have a recyclerview that displays a list of data. It has normal content for its rows as well as a footer. I don't see anything wrong in my code but when I try to build the project then I get the following error. I tried to clear the cache and invalidate my Android Studio but no luck. I still get the similar error in the console.

> Task :app:kaptDebugKotlin
/home/hesam/apps/projects/atco/rumi-android/app/build/generated/source/kapt/debug/com/atco/rumi/DataBinderMapperImpl.java:15: error: cannot find symbol
import com.atco.rumi.databinding.RowAboutItemBindingImpl;
                                ^
  symbol:   class RowAboutItemBindingImpl
  location: package com.atco.rumi.databinding
> Task :app:kaptDebugKotlin FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:kaptDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution
   > java.lang.reflect.InvocationTargetException (no error message)

I spent a few hours figuring out what is the problem but I couldn't. Does it compiler issue?

I do have databinding enabled in my app/build.gradle file.

...
   dataBinding {
        enabled = true
    }
...

This is my adapter class:

import android.content.Context
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.atco.rumi.BuildConfig
import com.atco.rumi.databinding.RowAboutFooterBinding
import com.atco.rumi.databinding.RowAboutItemBinding

data class AboutItem(
    val title: String,
    val icon: Drawable?,
    val webUrl: String
)

data class AboutFooter(
    val version: String
)

class AboutAdapter(
    val context: Context
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val items: List<AboutItem> = getAboutItems(context)

    companion object {
        const val TYPE_HEADER = 0
        const val TYPE_FOOTER = 1
        const val TYPE_ITEM = 2
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            TYPE_ITEM -> {
                val binding = RowAboutItemBinding.inflate(inflater)
                ItemViewHolder(binding)
            }
            TYPE_FOOTER -> {
                val binding = RowAboutFooterBinding.inflate(inflater)
                FooterViewHolder(binding)
            }
            else -> throw IllegalArgumentException("Invalid view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is FooterViewHolder -> {
                val version = getFormattedVersion(context, BuildConfig.VERSION_NAME)
                val item = AboutFooter(version)
                holder.bindItem(item)
            }
            is ItemViewHolder -> holder.bindItem(items[position], position)
            else -> throw IllegalArgumentException()
        }
    }

    override fun getItemCount(): Int {
        return items.size + 1 // Because we need to append the Footer
    }

    override fun getItemViewType(position: Int): Int {
        if (position == 0 && items.isEmpty()) return TYPE_FOOTER
        if (position == items.size) return TYPE_FOOTER
        return TYPE_ITEM
    }

    inner class ItemViewHolder(val binding: RowAboutItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bindItem(item: AboutItem, position: Int) {
            binding.item = item
            binding.aboutItemContainer.setOnClickListener {
                // TODO: Implement it
            }
            binding.executePendingBindings()
        }
    }

    inner class FooterViewHolder(val binding: RowAboutFooterBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bindItem(item: AboutFooter) {
            binding.item = item
            binding.executePendingBindings()
        }
    }
}

this is my row_about_item.xml file:

<?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="item"
            type="com.atco.rumi.scenes.tabs.about.AboutItem" />
    </data>

    <LinearLayout
        android:id="@+id/aboutItemContainer"
        style="@style/about_action_click">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="30dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="30dp"
            app:srcCompat="@{item.icon}" />

        <TextView
            android:id="@+id/textView"
            style="@style/about_textView"
            android:text="@{item.title}"
            tools:text="This is the title" />

    </LinearLayout>
</layout>

and finally, this is my row_about_footer.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="item"
            type="com.atco.rumi.scenes.tabs.about.AboutFooter" />
    </data>

    <LinearLayout style="@style/about_action_click">

        <TextView
            android:id="@+id/textView"
            style="@style/about_textView_footer"
            android:layout_marginStart="60dp"
            android:text="@{item.version}"
            tools:text="Version 1.0" />

    </LinearLayout>
</layout>

Solution

  • The suspect is srcCompat=@{item.icon}, as I'm not sure what exactly databinding generates for srcCompat (if anything). So I believe if you change this to

    <ImageView
            ...
            android:src="@{item.icon}"
    />
    

    this will work. If not, then you probably need to go for a custom binding adapter.