I am developing a demo app where i have to update the texview of an item in recyclerview whenever i change the value of edittext in the same item of recycler view. Following is my rcv adapter.
class RecyclerViewAdapterU (val dataList:ArrayList<ModelClass>): RecyclerView.Adapter<RecyclerViewAdapterU.ViewHolder>() {
var _binding: UploadItemViewBinding? = null
val binding get() = _binding!!
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerViewAdapterU.ViewHolder {
val v =
LayoutInflater.from(parent.context).inflate(R.layout.upload_item_view, parent, false)
_binding = UploadItemViewBinding.bind(v)
return ViewHolder(binding.root)
}
override fun onBindViewHolder(holder: ViewHolder, @SuppressLint("RecyclerView") position: Int) {
bindItems(dataList[position])
holder.getStock()
holder.updateStockDetail()
}
fun bindItems(data: ModelClass) {
binding.itemquant.text = data.item_quant
binding.uploadItemName.text = data.item_name
binding.uploadMfg.text = data.mfg
binding.skuStock.setText(data.item_stock.toString())
binding.skuCode.setText(data.sku_code)
}
override fun getItemCount(): Int {
return dataList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var stockdetail = ArrayList<ModelClass>()
fun getStock() {
binding.skuStock.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
for (i in 0 until RecyclerViewAdapter.ob.dataSelected.size){
if (editable.toString().trim()!=""){
var x= editable.toString().trim().toInt()
RecyclerViewAdapter.ob.dataSelected[adapterPosition].item_stock=x
}
}
}
})
}
fun updateStockDetail(){
binding.skuCode.addTextChangedListener(object : TextWatcher{
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
}
override fun afterTextChanged(editable: Editable) {
if (editable.length==3){
var x:String
for (i in 0 until RecyclerViewAdapter.ob.dataSelected.size){
if (editable.toString().trim()!=""){
x=editable.toString().trim()
RecyclerViewAdapter.ob.dataSelected[adapterPosition].sku_code=x
println("$x in if")
}
}
println(RecyclerViewAdapter.ob.dataSelected[adapterPosition].sku_code)
getUpdatedDetails(RecyclerViewAdapter.ob.dataSelected[adapterPosition].sku_code)
for (i in stockdetail){
bindItems(i)
//binding.uploadItemName.text=i.item_name
println(i.item_name)
}
}
}
})
}
fun getUpdatedDetails(skucode:String){
val call: Call<List<ModelClass>>? =
ApiClient.instance?.myApi?.getfromsku(skucode)!!
call!!.enqueue(object : Callback<List<ModelClass>?> {
override fun onResponse(
call: Call<List<ModelClass>?>,
response: Response<List<ModelClass>?>
) {
val skuDetails=response.body()
stockdetail.clear()
if (skuDetails != null) {
for (i in skuDetails){
println(i.item_name)
stockdetail.addAll(skuDetails)
}
}
}
override fun onFailure(call: Call<List<ModelClass>?>, t: Throwable) {
}
})
}
}
}
check out updatestockdetail function. Not sure if i am doing the right way.. if its not right can someone help me in doing it the right way?.
Detailed explanation of problem. Screenshot in this image B20 is sku code for Bingo20gm. in the same way, L20 is for Lays20gm, so if i change B20 to L20 the textview that is showing Bingo 20gm should change to Lays 20gm. data is being delivered by API, which i am calling in getupdateddetails(), and putting them in a list.
CodePudding user response:
This is more of a code review question, but you're overcomplicating this I think.
When you're using a RecyclerView
(or any list widget really) you have three parts:
- your data
- the thing that displays the data (a
RecyclerView
in this case) - the thing that takes data and works out how to display it (the
Adapter
)
In a RecyclerView
, the data gets displayed in mini layouts called ViewHolder
s. Instead of creating one for each item, a pool of them gets created, and when a ViewHolder
scrolls out of view it gets recycled (reused) to display a new item - which is why it's called a RecyclerView
(as opposed to a ListView
)
So each ViewHolder
is a thing that can display details for an item, and when onBindViewHolder
is called, that's when the adapter works out how to represent your data using the view holder - things like setting text, images, colours, checked status etc. You can also stick some variables in the ViewHolder
and set those during binding, so you have more info about what it's currently displaying - setting a position
variable is common. That way, you can put some code in the ViewHolder
that works with the data it currently represents.
Point is, you can make the ViewHolder
this self-contained component that:
- has an
EditText
and aTextView
- sets a
TextWatcher
on theEditText
when it's created - updates the
TextView
whenever theEditText
content changes
That's all internal to the ViewHolder
, it's just wiring up the components in its own layout
If you need the ViewHolder
to push some information (like your entered text, or a "button clicked" event) you probably want to put a function in your adapter that it can call:
fun updateThing(data: String, position: Int) {
// update your data sources, do any validation, etc
// call notifyDataSetChanged() or similar, if required
}
If you have a position
variable in your ViewHolder
(that you set during onBindViewHolder
) then the VH just needs to pass that when calling the function. Hey, here's an update, I'm showing position X and here's the new data. That way, the VH is just concerned with UI stuff, and all the data logic is kept separate in the adapter - and that can do things like deciding whether the list needs to update.
The other reason for doing that, is that the adapter handles updates from other sources too, like your API call. So it's much easier if all your data update logic is in one place, handled by one thing. That way, whatever the source of the update, whatever the reason is for needing an update, it's all done the same way. And if the adapter decides that the data has changed, and the display needs to update, that happens through the normal methods - i.e. onBindViewHolder
gets called, where you set the current data on the RecyclerView
. So you can get this chain of events:
EditText
onViewHolder
updatesTextWatcher
code calls the adapter'supdateThing
function with the new data- the adapter stores the new data and forces a display update
onBindViewHolder
gets called and displays the (now changed) data- the
ViewHolder
'sTextView
is set to show the new data
so in this case, you don't need to directly modify the TextView
at all, you just update the data, and as part of that process the new data will just get displayed. Because that's what should happen when the data is changed, whatever caused it to change. And you don't need to worry about what caused it, you just display it!
That was a long answer and I obviously haven't rewritten your code, but hopefully it gives you a better idea of how this works and how you can separate things and simplify them. You have a lot of code in your ViewHolder
that manipulates data, pokes around at the adapter, iterates over all the items in it etc. - it should really just display stuff and react to UI events. Messing with the data is the adapter's job
CodePudding user response:
I tried re-working with your code, but some things didn't make sense, for example RecyclerViewAdapter.ob.dataSelected
. Where are you getting the RecyclerViewAdapter
instance from?
Hence, I've pasted below a snippet of an adapter code which I had written sometime ago which solves exactly the same purpose as you require.
class RecyclerViewAdapter(
val interactionListener: InteractionListener,
val onTextChangedListener: OnTextChangedListener
) : RecyclerView.Adapter<RecyclerViewAdapter.OptionViewHolder>() {
private val variantsList = ArrayList<VariantsItem>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionViewHolder {
val binding = SellVariantRowItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return OptionViewHolder(binding)
}
override fun onBindViewHolder(holder: OptionViewHolder, position: Int) {
holder.bind(variantsList[position])
}
override fun getItemCount() = variantsList.size
fun updateVariantsList(newList: ArrayList<VariantsItem>) {
variantsList.clear()
variantsList.addAll(newList)
notifyDataSetChanged()
}
inner class OptionViewHolder(
private val binding: SellVariantRowItemBinding
) : RecyclerView.ViewHolder(binding.root) {
private var subVariant: String = ""
@SuppressLint("SetTextI18n")
fun bind(variantsItem: VariantsItem) {
subVariant = variantsItem.subVariant
binding.apply {
binding.root.tag = "pos_$bindingAdapterPosition"
sizeTv.text = "Size (${bindingAdapterPosition 1})"
variantValue.text = variantsItem.variant
subVariantValue.text = variantsItem.subVariant
quantityET.setText(variantsItem.quantity.toString().takeIf { it != "0" }?:"1")
sellingPriceET.setText(variantsItem.listingPrice.toString())
marketPriceET.setText(variantsItem.labelPrice.toString())
setOnClickListeners(variantsItem)
setTextChangeListeners(variantsItem)
}
}
private fun setTextChangeListeners(item: VariantsItem) {
binding.apply {
quantityET.addTextChangedListener {
if (it.toString().toIntOrNull() != null) {
onTextChangedListener.onQuantityChanged(
bindingAdapterPosition,
item.subVariant, item.variant,
it.toString()
)
}
}
marketPriceET.addTextChangedListener {
if (it.toString().toIntOrNull() != null) {
onTextChangedListener.onMarketPriceChanged(
bindingAdapterPosition,
item.subVariant,item.variant,
it.toString()
)
}
}
sellingPriceET.addTextChangedListener {
if (it.toString().toIntOrNull() != null){
onTextChangedListener.onSellingPriceChanged(
bindingAdapterPosition,
item.subVariant,item.variant,
it.toString()
)
}
}
}
}
private fun setOnClickListeners(variantsItem: VariantsItem) {
binding.apply {
deleteVariantBtn.setSafeOnClickListener {
interactionListener.onDeleteCard(variantsItem)
}
subVariantLayout.setSafeOnClickListener {
interactionListener.onChangeSubVariant(variantsItem)
}
variantLayout.setSafeOnClickListener {
interactionListener.onChangeVariant(variantsItem)
}
addNewVariant.setSafeOnClickListener {
it.pan()
interactionListener.onAddVariant(variantsItem.subVariant)
}
}
}
}
interface InteractionListener{
fun onChangeSubVariant(subToBeDeleted: VariantsItem)
fun onChangeVariant(variant: VariantsItem)
fun onDeleteCard(variant: VariantsItem)
fun onAddVariant(subVariantValue: String)
}
interface OnTextChangedListener{
fun onQuantityChanged(position: Int, subVariant: String, variant: String, quantity: String)
fun onMarketPriceChanged(position: Int, subVariant: String, variant: String, price: String)
fun onSellingPriceChanged(position: Int, subVariant: String, variant: String, price: String)
}
}
Few things to note:
- You cannot have a single
binding
instance to handle all of the items of the recycler view. We have theViewHolder
for this purpose. - Keep your binding logic and everything related to each individual item inside the ViewHolder. That's what it is meant for.
- Try keeping your adapter simple and only write presentation logic. Keep all of the business logic outside the adapter.
- Interfaces are your best friends. This supports point 3. Make your activity / fragment implement these interfaces and pass the instance to the Adapter constructor as such
val variantsAdapter = RecyclerViewAdapter(this, this)
. This will allow you to write your business logic outside the Recycler View adapter (Including context required statements like calling a different activity or starting a service or as in your case, making an API call)
Feel free to reach out in case you have any questions.