Items in RecyclerView are different with ViewBinding - android

When i make Adapter in regular way, with findViewById it looks ok: like this, but when i do it with view binding looks like this 2
Adapter with ViewBinding:
class HomePageFoldersAdapter() : RecyclerView.Adapter<HomePageFoldersAdapter.FolderHolder>() {
var list = emptyList<Folder>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FolderHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = FolderHomeCardBinding.inflate(inflater)
return FolderHolder(binding)
}
override fun onBindViewHolder(holder: FolderHolder, position: Int) {
val folder = list[position]
with(holder.binding) {
val context = root.context
nameTextView.text = folder.name
sizeTextView.text = context.getString(R.string.modules_count, folder.modulesIds.size)
}
}
override fun getItemCount() = list.size
class FolderHolder(val binding: FolderHomeCardBinding) : RecyclerView.ViewHolder(binding.root)
}

try replace instead
val binding = FolderHomeCardBinding.inflate(inflater)
with
val binding = FolderHomeCardBinding.inflate(inflater, parent, false)

Related

Cant seem to get my adapter to add my list to recyclerView

Id like to initially have a list show up on my recyclerView before implementing some kind of edit function to it. However, no matter what I do, the list I made wont show up. I've got a feeling that I'm doing something wrong with the bind function.
Here is my adapter
class WorkoutAdaptor (
var workouts: List<Workout>
) : RecyclerView.Adapter<WorkoutAdaptor.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val workoutCardBinding = WorkoutCardBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
return ViewHolder(workoutCardBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(workouts[position])
}
override fun getItemCount(): Int {
return workouts.size
}
inner class ViewHolder(private val workoutCardBinding: WorkoutCardBinding) :
RecyclerView.ViewHolder(workoutCardBinding.root) {
fun bind(workout: Workout) {
workoutCardBinding.apply {
tvWorkoutCard
}
}
}
}
and here is my main activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnNext.setOnClickListener {
Intent(this, SecondActivity::class.java).also {
startActivity(it)
}
}
// var workout = intent.getSerializableExtra("EXTRA_WORKOUT")
// binding.tvWorkoutCard.text = workout.toString()
var workoutList = mutableListOf(
Workout("a","d","d","d"),
Workout("a","d","d","d"),
Workout("a","d","d","d"),
Workout("a","d","d","d"),
Workout("a","d","d","d"),
)
val adaptor = WorkoutAdaptor(workoutList)
binding.recyclerView.adapter = adaptor
binding.recyclerView.layoutManager = LinearLayoutManager(this)
}
}
Change your bind method to this.
fun bind(workout: Workout) {
workoutCardBinding.textview.text = workout.objectName
}

trying use view binding with recycler view adaptor

Where in the code should i be using the following lines to work with my widgets:
binding = WorkoutCardBinding.inflate(layoutInflater)
setContentView(binding.root)
I know it should be used in an activiy's onCreate function but I can't seem to get it to work with the below adapter class
class WorkoutAdaptor (
var workouts: List<Workout>
) : RecyclerView.Adapter<WorkoutAdaptor.WorkoutViewHolder>() {
private lateinit var binding: WorkoutCardBinding
inner class WorkoutViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkoutViewHolder {
binding = WorkoutCardBinding.inflate(layoutInflater)
setContentView(binding.root)
val view = LayoutInflater.from(parent.context).inflate(R.layout.workout_card, parent, false)
return WorkoutViewHolder(view)
}
override fun onBindViewHolder(holder: WorkoutViewHolder, position: Int) {
holder.itemView.apply {
binding.tvWorkoutCard.text = workouts[position].
}
}
override fun getItemCount(): Int {
return workouts.size
}
val binding = WorkoutCardBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
return WorkoutViewHolder(binding)
//Change you onCreateViewHolder completely to this code
You don't have to create a binding variable in the adapter class. To use view binding in recyclerView adapter there are a few things that you've to change.
Firstly, change your viewHolder class constructor to accept viewBinding instead of a view
inner class WorkoutViewHolder(private val workoutCardBinding: WorkoutCardBinding) :
RecyclerView.ViewHolder(workoutCardBinding.root) {
fun bind(workout: Workout) {
workoutCardBinding.apply {
// create your view here
}
}
Change onCreateViewHolder, to return a viewHolder object using binding.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkoutViewHolder {
val workoutCardBinding=
WorkoutCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return WorkoutCardBinding(workoutCardBinding)
}
Your complete adapter class will look like this -
class WorkoutAdaptor (
var workouts: List<Workout>
) : RecyclerView.Adapter<WorkoutAdaptor.WorkoutViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkoutViewHolder {
val workoutCardBinding =
WorkoutCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return WorkoutCardBinding(workoutCardBinding)
}
override fun onBindViewHolder(holder: WorkoutViewHolder, position: Int) {
holder.bind(workouts[position])
}
override fun getItemCount(): Int {
return workouts.size
}
inner class WorkoutViewHolder(private val workoutCardBinding: WorkoutCardBinding) :
RecyclerView.ViewHolder(workoutCardBinding.root) {
fun bind(workout: Workout) {
workoutCardBinding.apply {
// create your view here
}
}
}
}

lateinit property binding has not been initialized

I know that there are similar questions to this, but I just cant find something that is similar, I've been studying new things to learn, and while converting kotlin synthetics to viewvbinding mode, I've encountered this error
kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized
at com.codepalace.chatbot.ui.MessagingAdapter.onBindViewHolder(MessagingAdapter.kt:60)
at com.codepalace.chatbot.ui.MessagingAdapter.onBindViewHolder(MessagingAdapter.kt:17)
It says that I have to initialize the binding, but I dont know where to put it.
This is the code.
class MessagingAdapter: RecyclerView.Adapter<MessagingAdapter.MessageViewHolder>() {
var messagesList = mutableListOf<Message>()
private lateinit var binding: MessageItemBinding
inner class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener {
//Remove message on the item clicked
messagesList.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
return MessageViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.message_item, parent, false)
)
}
override fun getItemCount(): Int {
return messagesList.size
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
val currentMessage = messagesList[position]
when (currentMessage.id) {
SEND_ID -> {
holder.itemView.findViewById<View>(R.id.tv_message).apply {
binding.tvMessage.text = currentMessage.message
visibility = View.VISIBLE
}
holder.itemView.findViewById<View>(R.id.tv_bot_message).visibility = View.GONE
}
RECEIVE_ID -> {
holder.itemView.findViewById<View>(R.id.tv_bot_message).apply {
binding.tvBotMessage.text = currentMessage.message
visibility = View.VISIBLE
}
holder.itemView.findViewById<View>(R.id.tv_message).visibility = View.GONE
}
}
}
fun insertMessage(message: Message) {
this.messagesList.add(message)
notifyItemInserted(messagesList.size)
}
private lateinit var binding: MessageItemBinding
You didn't initialize the binding object, as you defined it as lateinit, then you should define it.
This is typically in onCreateViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackedActivityHolder {
val inflater = LayoutInflater.from(parent.context)
binding = DataBindingUtil.inflate<MessageItemBinding>(inflater, R.layout.message_item, parent, false)
return MessageViewHolder(
binding
)
}
UPDATE
You need to accept MessageItemBinding type instead of View in the MessageViewHolder constructor, and use itemView.root to get the root view of the list item.
inner class MessageViewHolder(itemView: MessageItemBinding) : RecyclerView.ViewHolder(itemView.root) {
init {
itemView.root.setOnClickListener {
//Remove message on the item clicked
messagesList.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
}
}
}
The reason why You get this error is that MessageItemBinding should not be in Adapter but in ViewHolder class. You can make RecyclerView like this:
object MessageDiffCallback : DiffUtil.ItemCallback<Message>()
{
override fun areItemsTheSame(
oldItem: Message,
newItem: Message
): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(
oldItem: Message,
newItem: Message
): Boolean =
oldItem == newItem
}
I assume that Message is a data class. You have to create this MessageDiffCallback and override these 2 methods in a similar way that I did it.
Now Create ViewHolder class:
class MessageViewHolder private constructor(
private val binding: MessageItemBinding
) : RecyclerView.ViewHolder(binding.root)
{
companion object
{
fun create(parent: ViewGroup): MessageViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = MessageItemBinding.inflate(layoutInflater, parent, false)
return MessageViewHolder(
binding
)
}
}
fun bind(
message: Messaage
)
{
// Here You can do everything.
// You pass the message and based on this You can set up a view and use binding
}
}
And now Adapter class
class MessageAdapter : ListAdapter<Message, MessageViewHolder>(MessageDiffCallback)
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder =
MessageViewHolder.create(parent)
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) =
holder.bind(
message = getItem(position),
)
}
Now You should have a working adapter. To put data in it use messageAdapter.submitList(messages) in Your Fragment/Activity
Maybe not the best answer because it changes Your code and uses a little different logic but it should work better. You can check google sample code here or take codelab here. It is free after signing up

How to I access button inside a recycler view?

This is my RecyclerView Adaper
class RecyclerAdapter(private val recyclerList: List): RecyclerView.Adapter(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): mainRecyclerViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.quiz_item_recycler_view,
parent, false)
return mainRecyclerViewHolder(itemView)
}
override fun getItemCount() = recyclerList.size
override fun onBindViewHolder(holder: mainRecyclerViewHolder, position: Int) {
val currentItem = recyclerList[position]
holder.imageView.setImageResource(currentItem.imageResource)
holder.textView.text = currentItem.recyclerCardText
}
class mainRecyclerViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val imageView: ImageView = itemView.rec_image
val textView: TextView = itemView.text_view_1
}
}
And this is my data class
data class RecyclerItemMain(val imageResource: Int, val recyclerCardText: String, val button: Button)
Like this:
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.yourButton.setOnClickListener { v ->
// Here your start your other activity or navigate to another fragment
}
}
Remember that if you intent to send the user to diferent activities depending on the button, you have to create a when expression what will send the user to an activity depending on another value in the recyclerItem, for example the text of the item, in your case currentItem.recyclerCardText
Here is your full adapter, re-organized:
class RecyclerAdapter(recyclerList: List<CategorySectionIcon>) :
RecyclerView.Adapter<YourAdapter.CustomViewHolder>() {
private var recyclerList: List<CategorySectionIcon>? = recyclerList
inner class CustomViewHolder(
//Get a reference to the Views in our layout//
val myView: View
) : RecyclerView.ViewHolder(myView) {
var textView: TextView = myView.findViewById(R.id.your_text)
var imageView: ImageView = myView.findViewById(R.id.your_image)
var yourButton: Button = myView.findViewById(R.id.iv_category_imagen_icon)
}
override//Construct a RecyclerView.ViewHolder//
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemView =
layoutInflater.inflate(R.layout.quiz_item_recycler_view, parent, false)
return CustomViewHolder(itemView)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.textView.text = recyclerList!![position].recyclerCardText
holder.imageView.setImageResource(recyclerList!![position].image)
holder.yourButton.setOnClickListener { v ->
// Here your start your other activity or navigate to another fragment
}
}
//Calculate the item count for the RecylerView//
override fun getItemCount(): Int {
return recyclerList!!.size
}
}

Having trouble getting position on the 2nd Array

I'm Using GSON to parse my JSON. I have an object that has 2 array object. I'm having a problem on getting the TransactionDetails position.
I have a recyclerView that populates the "Transactions" and then when I tap on that it should open the "TransactionDetails". But I'm having trouble on getting the position on TransactionDetails what array to read.
As you can see on the code I need to put a position on [0] because it only loads the 1st array on transaction details.
JSON Url
DetailedTransactionAdapter
class DetailedTransactionAdapter(val transactionFeed: TransactionFeed) : RecyclerView.Adapter<DetailCustomViewHolder>() {
override fun onBindViewHolder(holder: DetailCustomViewHolder, position: Int) {
val tr = transactionFeed.Transactions[1].TransactionDetails[position]
holder.view.txt_programType.text = transactionDetail.ProgramType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailCustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.list_detailed_transaction, parent, false)
return DetailCustomViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return transactionFeed.Transactions[0].TransactionDetails.count()
}
}
class DetailCustomViewHolder(val view: View): RecyclerView.ViewHolder(view) {
init{
}
}
class TransactionFeed(val Transactions: List<Transactions>)
class Transactions(val TransactionCode: String,
val ProgramID: Int,
val ProgramName: String,
val ProgramType: String,
val ProgramDescription: String,
val TransactionID: Int,
val UserID: String,
val TransactionAmount: Int,
val TransactionDate: String,
val TransactionDetails: List<TransactionDetails>)
class TransactionDetails(val Clamied: Boolean,
val NextToClaim: Boolean,
val ProgramDetailID: Int,
val TransactionDetailID: Int,
val ProgramDetails: String,
val ProgramType: String,
val TransactionDetailAmount: Int,
val TransactionDetailMonth: String)
TransactionsAdapter
lass TransactionsAdapter(val transactionFeed: TransactionFeed) : RecyclerView.Adapter<CustomViewHolder>() {
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val tr = transactionFeed.Transactions.get(position)
holder.view.txt_transaction_id.text = tr.TransactionID.toString()
holder.view.txt_transaction_date.text = tr.TransactionDate
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.list_transaction, parent, false)
return CustomViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return transactionFeed.Transactions.count()
}
}
class CustomViewHolder(val view: View):RecyclerView.ViewHolder(view) {
init{
view.setOnClickListener {
val intent = Intent(view.context, DetailedTransactionActivity::class.java)
view.context.startActivity(intent)
}
}
}
You'll have to set appropriate position in getItemCount() and onBindViewHolder() of DetailedTransactionAdapter. Here is how you can go about doing it:
First pass selected transaction position in TransactionsAdapter (when user clicks on any transaction) via intent to DetailedTransactionActivity.
class CustomViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
init {
view.setOnClickListener {
val intent = Intent(view.context, DetailedTransactionActivity::class.java)
intent.putExtra("selectedTransaction", adapterPosition)
view.context.startActivity(intent)
}
}
}
Then get that position in DetailedTransactionActivity and pass it to DetailedTransactionAdapter.
val transactionPos = intent.getIntExtra("selectedTransaction", -1)
detailedTransactionAdapter = DetailedTransactionAdapter(transactionFeed, transactionPos)
You'll have to change your DetailedTransactionAdapter to also have selectedTransaction as a field. This will help in dynamically populating adapter and binding appropriate data to view. Change adapter impl to this:
class DetailedTransactionAdapter(val transactionFeed: TransactionFeed, val selectedTransaction:Int) : RecyclerView.Adapter<DetailCustomViewHolder>() {
override fun onBindViewHolder(holder: DetailCustomViewHolder, position: Int) {
if(selectedTransaction == -1){
return
}
val tr = transactionFeed.Transactions[selectedTransaction].TransactionDetails[position]
holder.view.txt_programType.text = transactionDetail.ProgramType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailCustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.list_detailed_transaction, parent, false)
return DetailCustomViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return transactionFeed.Transactions[selectedTransaction].TransactionDetails.count()
}
}
This is where magic happens. You are returning appropriate transaction details count
return transactionFeed.Transactions[selectedTransaction].TransactionDetails.count()
and binding data.
val tr = transactionFeed.Transactions[selectedTransaction].TransactionDetails[position]
So when you have selected first transaction, this will return count of transaction details of transaction[0] which is first transaction. And when any other transaction is selected, this will return its transaction details and populate adapter with its data.

Categories

Resources