BindingAdapter is not working as expected - android

I am trying to add a custom binding to my TextView. Please find my code below
MainActivity.kt
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.builders.bindingadaptertest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val model = ViewModelProvider(this).get(MainViewModel::class.java)
}
}
MainViewModel.kt
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MainViewModel: ViewModel() {
var data = MutableLiveData<String>()
init {
data.value = "This works"
}
}
activity_main.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="viewModel"
type="com.builders.bindingadaptertest.MainViewModel" />
</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"
android:text="Hello World!"
app:customBind="#{viewModel.data}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
BindingAdapters.kt
#BindingAdapter("app:customBind")
fun customBind(view: TextView, data: String?) {
Log.d("DEBUG_CUSTOM", "Setting custom [$data] listner")
view.text = data
}
Doubt: When I run this project, The log cat shows the data passing to customBind is null.
2020-11-30 09:37:33.351 4682-4682/com.builders.bindingadaptertest D/DEBUG_CUSTOM: Setting custom [null] listner
Can anyone help me to get the correct data from ViewModel into the binding adapter?

It's null, because in your databinding viewModel is not set.
You forgot to add binding.viewModel = model
Also, some possible suggestions:
No need to leave the LiveData reference mutable, just use val
I'd prefer an extension function for the binding adapter: fun TextView.customBind(data: String?)
You should set a lifecycleOwner on your binding, otherwise your activity may keep an indirect reference on your vm, which will cause leaks on orientation changes

Related

RecyclerView functions aren't called

I am trying to show a list of transactions in a recycler view.
Each transaction is represented by a CardView inside a constraint layout (see item_transaction.xml).
Somehow, the functions of my RecyclerView Adapter (onCreateViewHolder, onBindViewHolder and getItemCount) are never called (logs are never displayed - I removed most of them so the code is easier to read).
Therefore, the RecyclerView content doesn't display on my app.
It is also worth pointing out that I have a list of transactions called data in my adapter. Whenever I set the data in the list, it does update. No issues there.
💡 I know notifyDataSetChanged() isn't clean, I'll change it later on when I manage to get everything working.
Here is a preview of what I want so you can grasp the idea better :
EDIT : My fragment displays normally and has no problem executing code.
Hang on tight because there is a lot of code :
package com.example.manage.manageit.database
class Transaction(
var value: Long = 0,
var note: String = "",
var budget: String = "",
var creationTime: Long = System.currentTimeMillis()
)
Transaction.kt
package com.example.manage.manageit.home
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.example.manage.manageit.databinding.ActivityHomeBinding
import androidx.fragment.app.Fragment
import com.example.manage.manageit.R
import com.example.manage.manageit.about.AboutFragment
import com.example.manage.manageit.budget.TransactionFragment
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_home)
binding.bottomNavigation.menu.getItem(0).isCheckable = true
setFragment(HomeFragment())
binding.bottomNavigation.setOnItemSelectedListener {menu ->
when(menu.itemId){
R.id.homeFragmentButton -> {
setFragment(HomeFragment())
true
}
R.id.transactionFragmentButton -> {
setFragment(TransactionFragment())
true
}
R.id.aboutFragmentButton -> {
setFragment(AboutFragment())
true
}
else -> false
}
}
}
private fun setFragment(fr : Fragment){
val frag = supportFragmentManager.beginTransaction()
frag.replace(R.id.myNavHostFragment,fr)
frag.commit()
}
}
HomeActivity.kt : It contains a BottomNavigationView and my fragment.
package com.example.manage.manageit.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.manage.manageit.R
import com.example.manage.manageit.adapters.TransactionAdapter
import com.example.manage.manageit.database.Transaction
import com.example.manage.manageit.databinding.FragmentHomeBinding
import java.util.logging.Logger
class HomeFragment : Fragment() {
private var logger = Logger.getLogger(HomeFragment::class.java.name)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentHomeBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_home,
container,
false
)
val recyclerView: RecyclerView = binding.transactionList
val transactionAdapter = TransactionAdapter()
recyclerView.adapter = transactionAdapter
recyclerView.layoutManager = LinearLayoutManager(context)
transactionAdapter.data = listOf(
Transaction(10, "Transaction note", "Groceries"),
Transaction(20, "Transaction note", "Groceries"),
Transaction(30, "Transaction note", "Groceries")
)
return inflater.inflate(R.layout.fragment_home, container, false)
}
}
HomeFragment.kt
package com.example.manage.manageit.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.manage.manageit.R
import com.example.manage.manageit.database.Transaction
import java.util.logging.Logger
class TransactionAdapter : RecyclerView.Adapter<TransactionViewHolder>() {
private var logger = Logger.getLogger(this::class.java.name)
var data = listOf<Transaction>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder {
logger.info("adapter : onCreateViewHolder()")
return TransactionViewHolder.from(parent)
}
override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) {
val item = this.data[position]
holder.bind(item)
}
override fun getItemCount(): Int {
logger.info("adapter : getItemCount()")
return this.data.size
}
}
class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val icon: ImageView = itemView.findViewById(R.id.transaction_icon)
private val note: TextView = itemView.findViewById(R.id.transaction_note)
private val value: TextView = itemView.findViewById(R.id.transaction_value)
fun bind(item: Transaction) {
icon.setImageResource(R.drawable.ic_dollar_sign)
note.text = item.note
value.text = item.value.toString()
}
companion object {
fun from(parent: ViewGroup): TransactionViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.item_transaction, parent, false)
return TransactionViewHolder(view)
}
}
}
TransactionAdapter.kt
<?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"
tools:context="com.example.manage.manageit.home.HomeActivity">
<RelativeLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/myNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"/>
</LinearLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#android:color/black"
android:paddingVertical="6dp"
app:itemIconSize="28dp"
app:itemIconTint="#color/nav_bar_item"
app:itemRippleColor="#color/ripple_color"
app:itemTextColor="#color/nav_bar_item"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_navigation_menu" />
</RelativeLayout>
</layout>
activity_home.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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black"
android:fillViewport="true"
tools:context="com.example.manage.manageit.home.HomeFragment">
<TextView
android:id="#+id/homeActivityTitle"
style="#style/activityTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginTop="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:text="#string/home_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/transaction_list"
android:layout_width="match_parent"
android:layout_height="400dp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="#+id/floatingActionButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/homeActivityTitle" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="#dimen/medium_margin"
android:layout_marginBottom="#dimen/very_big_margin"
android:backgroundTint="#color/secondaryColor"
android:clickable="true"
android:contentDescription="#string/add_button_description"
android:src="#drawable/ic_plus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:maxImageSize="32dp"
tools:ignore="RedundantDescriptionCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
app:cardBackgroundColor="#android:color/transparent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#drawable/transaction">
<ImageView
android:id="#+id/transaction_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:contentDescription="Icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_dollar_sign"
app:tint="#android:color/white" />
<TextView
android:id="#+id/transaction_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:text="Transaction note"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#+id/transaction_icon"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/transaction_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="30dp"
android:text="55€"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
item_transaction.xml : Basically a card view with an image and two text views (the transaction "note" and value).
In onCreateView, you have created your view layout twice. The first one you did using DataBindingUtil and that's the one whose RecyclerView you set up. But then you let that whole layout go back to the garbage collector because you create a brand new layout using layoutInflater and return that layout on the last line of onCreateView.
Technically, you should not be setting up views in onCreateView anyway. It should be done in onViewCreated(), although I don't think it makes much difference. However, since Fragment provides a constructor that can automatically inflate a provided layout ID, I think it's cleaner anyway to eliminate onCreateView() entirely, like this:
class HomeFragment : Fragment(R.layout.fragment_home) {
private var logger = Logger.getLogger(HomeFragment::class.java.name)
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
val binding: FragmentHomeBinding = DataBindingUtil.bind(view)
val recyclerView: RecyclerView = binding.transactionList
val transactionAdapter = TransactionAdapter()
recyclerView.adapter = transactionAdapter
recyclerView.layoutManager = LinearLayoutManager(context)
transactionAdapter.data = listOf(
Transaction(10, "Transaction note", "Groceries"),
Transaction(20, "Transaction note", "Groceries"),
Transaction(30, "Transaction note", "Groceries")
)
}
}

(Android Kotlin) RecyclerView Adapter methods not called: empty RecyclerView being shown

I am trying to display JSON data from a dummy server (json-server) using Retrofit2 and display it in a RecyclerView placed inside a fragment. So far the following events are taking place (found out using Log infos):
Main fragment CharactersListFragment.kt creation is initiated.
Only getItemCount() is being called. It is returning 0.
OnCreateViewHolder() and onBindViewHolder() are not being called.
Data is successfully fetched from the server and passed to the adapter (from CharactersListFragment.kt)
There are no compile time errors nor does my app crash, just the RecyclerView is empty.
Here are my code files:
Layout for fragment with RecyclerView: fragment_characters_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".CharactersListFragment">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recycler_view"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Layout for RecyclerView row: fragment_character_recyclerview_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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"
android:id="#+id/character_card_view"
android:layout_width="match_parent"
android:layout_height="300dp"
android:elevation="15dp"
app:cardBackgroundColor="#color/violet_forcard"
app:cardCornerRadius="3dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/char_thumnail_img"
android:layout_width="143dp"
android:layout_height="150dp"
android:layout_marginStart="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.106"
tools:srcCompat="#tools:sample/avatars" />
<TextView
android:id="#+id/char_id_txt"
android:layout_width="190dp"
android:layout_height="53dp"
android:layout_marginTop="16dp"
android:fontFamily="#font/roboto"
android:text="Character ID: "
android:textAlignment="viewStart"
android:textColor="#color/white"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/char_thumnail_img"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/char_name_txt"
android:layout_width="190dp"
android:layout_height="53dp"
android:layout_marginTop="24dp"
android:fontFamily="#font/roboto"
android:text="Name: "
android:textAlignment="viewStart"
android:textColor="#color/white"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/char_thumnail_img"
app:layout_constraintTop_toBottomOf="#+id/char_id_txt" />
<TextView
android:id="#+id/char_descp_txt"
android:layout_width="359dp"
android:layout_height="wrap_content"
android:fontFamily="#font/roboto"
android:textAlignment="viewStart"
android:textColor="#color/white"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.461"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.918" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Kotlin class for fragment: CharactersListFragment.kt
package com.example.marvelapp
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class CharactersListFragment : Fragment() {
private lateinit var charsAdapter: CharacterListAdapter
private lateinit var apiService: APIService
private var characters: MutableList<Character> = ArrayList()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
Log.i("INSIDE_FRAGMENT_ONCREATEVIEW", "Fragment creation initiated")
val v = inflater.inflate(R.layout.fragment_characters_list, container, false)
apiService = RestClient.client.create(APIService::class.java)
getCharacterList()
val recycler_view = v.findViewById(R.id.recycler_view) as RecyclerView
recycler_view.layoutManager = LinearLayoutManager(activity)
charsAdapter = CharacterListAdapter()
recycler_view.adapter = charsAdapter
return v
}
private fun getCharacterList(){
val call = apiService!!.get_characters()
call.enqueue(object: Callback<List<Character>> {
override fun onResponse(
call: Call<List<Character>>,
response: Response<List<Character>>
) {
val chars = response.body()
Log.i("get_character_succeeded_FOR_CALL", chars.toString())
if (chars != null){
characters.addAll(chars!!)
Log.i("character_ADD_CHECK_INSIDE_FRAGMENT", characters[0].toString())
charsAdapter.submitDataforcharList(characters)
}
}
override fun onFailure(call: Call<List<Character>>, t: Throwable) {
Log.e("get_character_failed", t.localizedMessage)
}
})
}
}
Kotlin class for adapter: CharacterListAdapter.kt
package com.example.marvelapp
import android.text.Layout
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_character_recyclerview_list.view.*
class CharacterListAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var charItems: List<Character> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
Log.i("INSIDE_ONCREATE_VIEWHOLDER", "reached")
return CharacterListViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.fragment_character_recyclerview_list,
parent,
false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder){
is CharacterListViewHolder ->{
Log.i("VIEWHOLDER_FOUND", "proceeding to bind")
holder.bind(charItems.get(position))
}
}
}
override fun getItemCount(): Int {
Log.i("INSIDE_ADAPTER_GET_ITEM_COUNT", charItems.size.toString())
return charItems.size
}
fun submitDataforcharList (characterlist: List<Character>){
Log.i("INSIDE_ADAPTER_SUBMIT_DATA", characterlist[0].toString())
charItems = characterlist
}
class CharacterListViewHolder
constructor(view: View): RecyclerView.ViewHolder(view){
val char_id = view.findViewById<TextView>(R.id.char_id_txt)
val char_name = view.findViewById<TextView>(R.id.char_name_txt)
val char_descp = view.findViewById<TextView>(R.id.char_descp_txt)
fun bind(character: Character){
Log.i("INSIDE_VIEWHOLDER_BIND", character.toString())
val id = Integer.toString(character.charId)
val id_mess = "Character ID: $id"
char_id.setText(id_mess)
val char_name_mess = "Name: ${character.charName}"
char_name.setText(char_name_mess)
char_descp.setText(character.charDescp)
}
}
}
the Character model class:
package com.example.marvelapp
data class Character(
val charId: Int,
val charName: String,
val charDescp: String,
){
}
I am an android beginner and have been stuck at this for quite some time, any help will be highly appreciated.
Thanks in advance!
In the onCreateView of CharactersListFragment you are calling getCharacterList() and then you move on with your initialization of the recycler view. After the getCharacterList call completes, you might be getting some data and you are simply updating the data list in your adapter class but you are not informing your recyclerview to refresh the view to display the new data. So I think if you just call notifyDataSetChanged() on your adapter after setting the new data it should work.
The other way to solve the issue could be if you initialize your recyclerview after you've received the data from the getCharacterList() call and just show some loading icon in place of the recycler view while the data is being fetched.
getCharacterList() is called on UI thread (main thread) but communicating (requests and responses) between device and API server is happening in another thread, it means there is no guarantee that onResponse() will come earlier than recycler_view.adapter = charsAdapter and in reality, it mostly does not happens. Therefor, you must call charsAdapter.notifyDataSetChanged() after charsAdapter.submitDataforcharList(characters) to have the recyclerview represent data. Another more elegent way to do that is adding notifyDatasetChanged() inside submitDataforcharList():
fun submitDataforcharList (characterlist: List<Character>){
charItems = characterlist
notifyDatasetChanged()
}

RecyclerView does not bind data to ViewHolder unless it is notified

I'm trying to implement the RecyclerView with data binding.
The RecyclerView worked properly when I used findViewById to retrieve the widgets. But, if I use RecyclerView with data binding, the data are not bound to the ViewHolder.
MainActivity.kt
package com.example.wifilogger
import android.os.Bundle
import android.os.Handler
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.wifilogger.databinding.ActivityMainBinding
import com.example.wifilogger.helper.WiFi
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private var wifiList = ArrayList<WiFi>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.loading = true
wifiList.add(WiFi("38:BC:01:64:2D:38", "Example", -87, "WPA2"))
recycler_view.apply {
layoutManager = LinearLayoutManager(context)
adapter = WiFiAdapter(wifiList)
}
Handler().postDelayed({
recycler_view.adapter?.notifyDataSetChanged()
}, 1000)
}
}
WiFi.kt
data class WiFi(var BSSID: String, var SSID: String, var level: Int, var encryption: String)
WiFiAdapter
package com.example.wifilogger
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import com.example.wifilogger.databinding.ListItemBinding
import com.example.wifilogger.helper.WiFi
class WiFiAdapter(_wifiList: ArrayList<WiFi>) :
androidx.recyclerview.widget.RecyclerView.Adapter<WiFiAdapter.ViewHolder>() {
private val wifiList = _wifiList
class ViewHolder(_binding: ListItemBinding) : androidx.recyclerview.widget.RecyclerView.ViewHolder(_binding.root) {
var binding: ListItemBinding = _binding
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ListItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.list_item, parent, false
)
return ViewHolder(binding)
}
override fun getItemCount() = wifiList.size
override fun onBindViewHolder(holder: ViewHolder, pos: Int) {
holder.binding.ssid.text = wifiList[pos].SSID
holder.binding.bssid.text = wifiList[pos].BSSID
holder.binding.level.text = wifiList[pos].level.toString()
holder.binding.encryption.text = wifiList[pos].encryption
}
}
list_item.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:tool="http://schemas.android.com/tools">
<data>
<variable
name="ssid"
type="String" />
<variable
name="bssid"
type="String" />
<variable
name="level"
type="int" />
<variable
name="encryption"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/ssid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:textColor="#color/md_grey_900"
android:text="#{ssid}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tool:hint="SSID: Mum Use This One" />
<TextView
android:id="#+id/bssid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/md_grey_600"
android:text="#{bssid}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/ssid"
tool:hint="BSSID: 4A-C0-D0-07-91-E5" />
<TextView
android:id="#+id/level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:textColor="#color/md_grey_600"
android:text='#{level + "dBm"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tool:hint="-68 dBm" />
<TextView
android:id="#+id/encryption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/md_grey_600"
android:text="#{encryption}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/level"
tool:hint="WPA2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</layout>
The code works if I call notifyDataSetChanged() with a small delay.
Screenshot
But, if I remove that line, the data is not bound to the list_item.
Screenshot
I think some asynchronous task is running when binding the data to the ViewHolder, but I can't figure out where.
Any help would be appreciated. Thank you.
Try
class WiFiAdapter(_wifiList: ArrayList<WiFi>) :
androidx.recyclerview.widget.RecyclerView.Adapter<WiFiAdapter.ViewHolder>() {
...
override fun onBindViewHolder(holder: ViewHolder, pos: Int) {
...
holder.binding.executePendingBindings()
}
}

Kotlin Android - Unable to get #BindingAdapter to work

I keep getting the binding error when trying to use the #BindingAdapter. Try for 3 days and follow numerous online articles on this subject, but still getting the below error.
#BindingAdapter("focusableColor")
fun setFocusableColor(v:CardView, color:Int) {
println("hello")
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding:ActivityMainBinding =
DataBindingUtil.setContentView(this,R.layout.activity_main)
etc...
}
In current_task_layout.xml
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.edenhan.simplytask.Task">
</variable>
</data>
<android.support.v7.widget.CardView
android:id="#+id/card_view">
.....
focusableColor="#{1}"/>
Error encountered:
Found data binding errors.
****/ data binding error ****msg:Cannot find the setter for attribute ‘focusableColor’ with parameter type int on
android.support.v7.widget.CardView.
file:D:\…….\app\src\main\res\layout\current_task_layout.xml
Have you tried moving the binding out of the companion object?
You should put it in a kotlin file and make it a top level function. For example:
Bindings.kt
#BindingAdapter("focusableColor")
fun setFocusableColor(v:CardView, color:Int) {..}
And put the binding xml in the app namespace
Also, see Kotlin custom attribute databinding
edit: full example
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
#BindingAdapter("focusableColor")
fun setColor(card: CardView, #ColorInt color: Int) {
// or whatever
card.setBackgroundColor(color)
}
activity_main.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"
>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.CardView
android:id="#+id/card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:focusableColor="#{1}"/>
</android.support.constraint.ConstraintLayout>
</layout>
Below defined full code for load image using BindingAdapter with Kotlin
ImageLoader.kt
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.databinding.ObservableField
class ImageLoader {
val imageResource = ObservableField(R.drawable.ic_launcher_background)
companion object {
#JvmStatic
#BindingAdapter("android:src")
fun setImage(imageView: ImageView, imageRes: Int) {
imageView.setImageResource(imageRes)
}
}
}
activity_home.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">
<data>
<variable name="imageLoader" type="com.sample.testdemo.ImageLoader"/>
</data>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="#string/app_name"
android:src="#{imageLoader.imageResource}"
android:layout_centerInParent="true"/>
</RelativeLayout>
</layout>
HomeActivity.kt
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.sample.testdemo.databinding.ActivityHomeBinding
class HomeActivity : AppCompatActivity() {
lateinit var binding: ActivityHomeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_home)
binding.imageLoader = ImageLoader()
}
}
Note: Don't forget to add below line at the top of app level build.gradle
apply plugin: 'kotlin-kapt'

App crashes in Android Studio using Kotlin

I have started from the very basic level in Android and have created a simple app to display Happy Birthday. But the App keeps crashing in both the Emulator and the Actual hardware(SAMSUNG NOTE 8).
Need help on how can i resolve the error.
Here is xml Code :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.android.happybirthday.MainActivity">
<TextView
android:id="#+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_margin="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
MainActivity Code :
package com.example.android.happybirthday
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Example of a call to a native method
sample_text.text = stringFromJNI()
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String
companion object {
// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("native-lib")
}
}
}
You forget to typecast your TextView :
Try with below code :
val sample_text : TextView = findViewById<TextView>(R.id.sample_text) as TextView
Full Code :
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sample_text : TextView = findViewById<TextView>(R.id.sample_text) as TextView
sample_text .text = "Happy Birthday"
}
}

Categories

Resources