I've been trying to make RecyclerView work in my app where the list items are ImageViews, and the images get downloaded and put inside (asynchronously) in the onBindViewHolder method. I'm not facing any errors in my code, but for some reason
only the list items which will be visible (even partially) to the user
when the activity loads, have images loaded into them.
Though I can't see the images, I observed that the height and width of these items have been allocated correctly. And since the images get downloaded first, and then the ImageView's dimensions are determined I figure that the problem has got something to do with RecyclerView itself? If someone can shed some light on this, it would be great. Thanks.
I would also like to add, that if the Activity is paused and then resumed (by clicking on the "square" navigation button and then resuming it), the images of all the list items load correctly.
Pic #1
Pic #2
Here's my code:
onCreate method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
recyclerView {
id = ViewID.ID_LIST
}
val imgList = ArrayList<ImageView>()
imgList.add(ImageView(ctx))
imgList.add(ImageView(ctx))
imgList.add(ImageView(ctx))
imgList.add(ImageView(ctx))
val lv = findViewById(ViewID.ID_LIST) as RecyclerView
lv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
lv.adapter = ImageRecyclerAdapter(ctx, imgList)
}
The RecyclerView.Adapter class:
private class ImageRecyclerAdapter(val context: Context, val imageList: ArrayList<ImageView>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onViewRecycled(holder: RecyclerView.ViewHolder?) {
super.onViewRecycled(holder)
if (holder != null) {
val v = holder.itemView as ImageView
v.setImageBitmap(null)
}
}
override fun onBindViewHolder(p: RecyclerView.ViewHolder, position: Int) {
val v = p.itemView as ImageView
Ion.with(v)
.load("https://pbs.twimg.com/profile_images/616076655547682816/6gMRtQyY.jpg")
.setCallback({ exception, t ->
if (t != null) {
val dm = Point()
context.windowManager.defaultDisplay.getRealSize(dm)
val w = t.maxWidth
val h = t.maxHeight
val params = t.layoutParams
if (params != null) {
params.width = dm.x
params.height = (dm.x * (h.toDouble() / w.toDouble())).toInt()
t.layoutParams = params
t.requestLayout()
}
}
})
}
override fun getItemCount(): Int {
return imageList.size
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
val v = ImageView(context)
return object : RecyclerView.ViewHolder(v) {}
}
}
It worked after I made the binding of data into a Synchronous request, and I moved the ImageView manipulation (changing LayoutParams) into the onViewAttachedToWindow overridden method of my adapter.
onViewAttachedToWindow:
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder?) {
super.onViewAttachedToWindow(holder)
val t = holder?.itemView as ImageView
val dm = Point()
context.windowManager.defaultDisplay.getRealSize(dm)
val w = t.maxWidth
val h = t.maxHeight
val params = t.layoutParams
if (params != null) {
params.width = dm.x
params.height = (dm.x * (h.toDouble() / w.toDouble())).toInt()
t.layoutParams = params
t.requestLayout()
}
}
onBindViewHolder:
override fun onBindViewHolder(p: RecyclerView.ViewHolder, position: Int) {
val v = p.itemView as ImageView
Ion.with(v)
.load(imageList[position].toString())
.tryGet()
}
Related
So, I have a Recyclerview, where every element contains an Icon, a textview and a textedit.
The adapter looks like this:
class ZutatenSmallListAdaptermitEingabe internal constructor(
context: Context, parent: NewRezeptActivity
) : RecyclerView.Adapter<ZutatenSmallListAdaptermitEingabe.ZutatenViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var zutaten = emptyList<ZutatenData>()
private var mengenedits = emptyList<RefZutatRezept>()
private val context = context
private val parent = parent
inner class ZutatenViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val zutatenItemView: TextView = itemView.findViewById(R.id.textSmallZutat)
val zutatenPicView: ImageView = itemView.findViewById(R.id.image_SmallZutat)
var zutatenMengenedit : EditText = itemView.findViewById(R.id.textedit_menge)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ZutatenViewHolder {
var itemView :View = inflater.inflate(R.layout.recyclerview_item_small_mitmengeeingabe, parent, false)
return ZutatenViewHolder(itemView)
}
override fun onBindViewHolder(holder: ZutatenViewHolder, position: Int) {
val current = zutaten[position]
val curpos = position
holder.zutatenItemView.text = current.zutname
holder.zutatenPicView.setImageDrawable(ContextCompat.getDrawable(context, current.bild))
holder.zutatenItemView.setOnClickListener {
parent.removeZutat(zutaten[position])
}
var richtigmengenedit = mengenedits.firstOrNull() {it.zutatid == current.zutatid}
if( richtigmengenedit != null) {
holder.zutatenMengenedit.setText(richtigmengenedit.menge)
}
holder.zutatenMengenedit.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int){
mengenedits[curpos].menge = holder.zutatenMengenedit.text.toString()
}
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
})
}
internal fun setZutaten(zutats: List<ZutatenData>) {
this.zutaten = zutats
var neueListe = emptyList<RefZutatRezept>()
for(zut in zutaten){
var element = mengenedits.firstOrNull(){it.zutatid == zut.zutatid}
if( element!= null){
neueListe += element
}else{
neueListe += RefZutatRezept( zut.zutatid, 0, "")
}
}
mengenedits = neueListe
notifyDataSetChanged()
}
override fun getItemCount(): Int = zutaten.size
}
I have a list "zutaten" where I store the elements I want to display and I have another List "mengenedits", where I store the text the user writes into the textedit.
I also have two functions where I can add and remove elements from the list. Then the new List ist set with setZutatem.
When I add or remove an element I still want to show the right text in the textedit.
But the problem is when the List "zutaten" updates, the list of "mengedits" changes correctly, but in the onBindViewHolder function it somehow gets messed up. The text of the secon element shows in the textedit of the first.
So I am not shure what the porblem was, but I fixed it:
I am only adding and removing certain items, so instead of replacing the whole list, I just removed or added the specific item and instead of calling a notifyDataSetChanged(), I just called notifyItemInserted and notifyItemRemoved
So the functions for removing an adding looked like this:
fun addZutat(zutat: ZutatenData){
zutaten.add(zutat)
mengenedits.add( RefZutatRezept( zutat.zutatid, 0, ""))
notifyItemInserted(zutaten.size -1)
}
fun removeZutat(zutat: ZutatenData){
val pos = zutaten.indexOf(zutat)
zutaten.removeAt(pos)
mengenedits.removeAt(pos)
notifyItemRemoved(pos)
}
Note: I also change the lists to MutableLists, because I wanted to call removeAt()
i try to implement programmatically version of MotionLayout by extending it. And i have a base activity ayout using RecyclerView.
However, when i add my motion layout as an item of the RecyclerView, the view is not recycled when i try to scrolling up and down.
And it works well when i use as a normal view (act as single view).
Here is the preview:
class SimpleMotionLayout #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr) {
private val motionScene = MotionScene(this)
private var _simpleTransition: MotionScene.Transition? = null
private lateinit var squareView: View
init {
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
initDefaultConstraint(this)
setMotion()
}
fun setMotion() {
_simpleTransition = createPlaceholderTransition(motionScene)
setDebugMode(DEBUG_SHOW_PATH)
/**
* The order matters here.
* [MotionScene.addTransition] adds the transition to the scene while
* [MotionScene.setTransition] sets the transition to be the current transition.
*/
motionScene.addTransition(_simpleTransition)
motionScene.setTransition(_simpleTransition)
scene = motionScene
setTransition(_simpleTransition!!.id)
animateView()
}
fun setSquareColor(color: Int) {
squareView.setBackgroundColor(color)
}
fun initDefaultConstraint(motionLayout: ConstraintLayout) {
// View
squareView = View(context).apply {
id = R.id.default_button
setBackgroundColor(Color.BLACK)
}
motionLayout.addView(
squareView,
LayoutParams(
fromDp(context, 52),
fromDp(context, 52)
)
)
val set = ConstraintSet()
set.clone(motionLayout)
// Setup constraint set to TOP, LEFT to the Parent
set.connect(
squareView.id,
TOP,
PARENT_ID,
TOP
)
set.connect(
squareView.id,
START,
PARENT_ID,
START
)
set.applyTo(motionLayout)
}
private fun setToEnd() {
val endSet = getConstraintSet(_simpleTransition?.endConstraintSetId ?: return)
endSet.clear(R.id.default_button, START)
endSet.connect(
R.id.default_button,
END,
PARENT_ID,
END
)
}
fun animateView() {
setToEnd()
_simpleTransition?.setOnSwipe(
OnSwipe().apply {
dragDirection = DRAG_END
touchAnchorId = R.id.default_button
touchAnchorSide = SIDE_START
onTouchUp = ON_UP_AUTOCOMPLETE_TO_START
setMaxAcceleration(500)
}
)
setTransition(_simpleTransition!!.id)
}
// Placeholder transition??
fun createPlaceholderTransition(motionScene: MotionScene): MotionScene.Transition? {
val startSetId = View.generateViewId()
val startSet = ConstraintSet()
startSet.clone(this)
val endSetId = View.generateViewId()
val endSet = ConstraintSet()
endSet.clone(this)
val transitionId = View.generateViewId()
return TransitionBuilder.buildTransition(
motionScene,
transitionId,
startSetId, startSet,
endSetId, endSet
)
}
/**
* Get px from dp
*/
private fun fromDp(context: Context, inDp: Int): Int {
val scale = context.resources.displayMetrics.density
return (inDp * scale).toInt()
}
}
Below is my adapter:
class SimpleMotionLayoutAdapter : RecyclerView.Adapter<SimpleMotionLayoutAdapter.ViewHolder>() {
val items = mutableListOf<Int>() // colors
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun setColor(color: Int) {
(view as SimpleMotionLayout).setSquareColor(color)
}
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = SimpleMotionLayout(parent.context)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setColor(items[position])
}
override fun getItemCount(): Int = items.size
companion object {
const val TYPE_NORMAL = 0
const val TYPE_EXCEPTIONAL = 1
}
}
Am i missing implementation?
Thank you
In general you need to cache and restore the state of the MotionLayout when it gets Recycled.
Right now in onBindViewHolder you only set the Color.
Remember RecyclerView keeps a only a screens worth (+ about 3) of ViewHolders and reuses them using onBindViewHolder
At minimum you need to set the Progress of the MotionLayout.
Due to differences in timing you may need to set the progress in an onPost
I'm using navigation component in my app and for get data from api i'm using retrofit in MVVM architecture, i want get data from api and display in nested RecyclerView, this is worked and not problem for display data into nested Recylerview but when go to fragment detail and back to previous fragment not saved state and position item in horizontal list ,how to can display current position RecyclerView when back to previous fragment?
parent adapter
import kotlinx.android.synthetic.main.item_main_shaping_group.view.*
class MainShapingAdapter(
private val listGroup: MutableList<MainModel>,
private val listener: ListItemClick
) : RecyclerView.Adapter<MainShapingAdapter.MyViewHolder>(),
MainShapingChildAdapter.ListItemClickChild {
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_main_shaping_group, parent, false)
return MyViewHolder(layout)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.apply {
tv_titleGroup_itemGroup.text = listGroup[position].category.categoryTitle
rv_itemGroup.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, true)
rv_itemGroup.adapter = MainShapingChildAdapter(
listGroup[position].listProduct.toMutableList(),
this#MainShapingAdapter
)
rv_itemGroup.isNestedScrollingEnabled = false
rv_itemGroup.setRecycledViewPool(viewPool)
btn_more_itemGroup.setOnClickListener {
listener.itemOnclickCategory(listGroup[position].category)
}
}
}
override fun getItemCount(): Int = listGroup.size
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
interface ListItemClick {
fun itemOnclickCategory(category: CategoryModel)
fun itemOnclickChild(product: Product)
}
override fun childOnclick(product: Product) {
listener.itemOnclickChild(product)
}
override fun onViewRecycled(holder: MyViewHolder) {
super.onViewRecycled(holder)
Log.d(ConstantApp.TAG, "onViewRecycled 1")
}
}
childe adapter
import kotlinx.android.synthetic.main.item_main_shaping_child.view.*
class MainShapingChildAdapter(
private val listProduct: MutableList<Product>,
private val listener: ListItemClickChild
) : RecyclerView.Adapter<MainShapingChildAdapter.MyViewHolder>() {
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_main_shaping_child, parent, false)
return MyViewHolder(layout)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.apply {
Glide.with(context).load(listProduct[position].productCover)
.into(iv_coverProduct_shapingChild)
tv_titleProduct_shapingChild.text = listProduct[position].productTitle
tv_priceProduct_shapingChild.text = listProduct[position].productPrice.toString()
setOnClickListener {
listener.childOnclick(listProduct[position])
}
}
}
override fun getItemCount(): Int = listProduct.size
interface ListItemClickChild {
fun childOnclick(product: Product)
}
}
I used this tutorial to make my carousel recycler views hold their scroll state:
https://rubensousa.com/2019/08/27/saving_scroll_state_of_nested_recyclerviews/
Basically you have to create a new class:
import android.os.Bundle
import android.os.Parcelable
import androidx.recyclerview.widget.RecyclerView
/**
* Persists scroll state for nested RecyclerViews.
*
* 1. Call [saveScrollState] in [RecyclerView.Adapter.onViewRecycled]
* to save the scroll position.
*
* 2. Call [restoreScrollState] in [RecyclerView.Adapter.onBindViewHolder]
* after changing the adapter's contents to restore the scroll position
*/
class ScrollStateHolder(savedInstanceState: Bundle? = null) {
companion object {
const val STATE_BUNDLE = "scroll_state_bundle"
}
/**
* Provides a key that uniquely identifies a RecyclerView
*/
interface ScrollStateKeyProvider {
fun getScrollStateKey(): String?
}
/**
* Persists the [RecyclerView.LayoutManager] states
*/
private val scrollStates = hashMapOf<String, Parcelable>()
/**
* Keeps track of the keys that point to RecyclerViews
* that have new scroll states that should be saved
*/
private val scrolledKeys = mutableSetOf<String>()
init {
savedInstanceState?.getBundle(STATE_BUNDLE)?.let { bundle ->
bundle.keySet().forEach { key ->
bundle.getParcelable<Parcelable>(key)?.let {
scrollStates[key] = it
}
}
}
}
fun setupRecyclerView(recyclerView: RecyclerView, scrollKeyProvider: ScrollStateKeyProvider) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
saveScrollState(recyclerView, scrollKeyProvider)
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val key = scrollKeyProvider.getScrollStateKey()
if (key != null && dx != 0) {
scrolledKeys.add(key)
}
}
})
}
fun onSaveInstanceState(outState: Bundle) {
val stateBundle = Bundle()
scrollStates.entries.forEach {
stateBundle.putParcelable(it.key, it.value)
}
outState.putBundle(STATE_BUNDLE, stateBundle)
}
fun clearScrollState() {
scrollStates.clear()
scrolledKeys.clear()
}
/**
* Saves this RecyclerView layout state for a given key
*/
fun saveScrollState(
recyclerView: RecyclerView,
scrollKeyProvider: ScrollStateKeyProvider
) {
val key = scrollKeyProvider.getScrollStateKey() ?: return
// Check if we scrolled the RecyclerView for this key
if (scrolledKeys.contains(key)) {
val layoutManager = recyclerView.layoutManager ?: return
layoutManager.onSaveInstanceState()?.let { scrollStates[key] = it }
scrolledKeys.remove(key)
}
}
/**
* Restores this RecyclerView layout state for a given key
*/
fun restoreScrollState(
recyclerView: RecyclerView,
scrollKeyProvider: ScrollStateKeyProvider
) {
val key = scrollKeyProvider.getScrollStateKey() ?: return
val layoutManager = recyclerView.layoutManager ?: return
val savedState = scrollStates[key]
if (savedState != null) {
layoutManager.onRestoreInstanceState(savedState)
} else {
// If we don't have any state for this RecyclerView,
// make sure we reset the scroll position
layoutManager.scrollToPosition(0)
}
// Mark this key as not scrolled since we just restored the state
scrolledKeys.remove(key)
}
}
Then you use this class to store the state when the fragment/activity is detached/destroyed.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
scrollStateHolder = ScrollStateHolder(savedInstanceState)
return inflater.inflate(R.layout.layout, container, false)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
scrollStateHolder.onSaveInstanceState(outState)
}
You also have to use these two lines somewhere in your code:
scrollStateHolder.setupRecyclerView(itemView.child_recipes_rv, this)
scrollStateHolder.restoreScrollState(itemView.child_recipes_rv, this)
I'm saying 'somewhere' because that depends on your specific implementation. I did a variation of what the guy did in the tutorial, so that's up to you. In my case, those two lines are called one after the other when I'm building each child recycler view.
Basically you have to have an identifier for every child recyclerview. And you use that as a key in your map (see ScrollStateHolder.kt), and then when saving the state of the fragment/activity you're saving the state and that includes the scrolling state of the recyclerview.
What worked for me, while using this (Ruben Sousa's blog entry), was using a view model to store the bundle and use it on onDestroyView with scrollStateHolder?.onSaveInstanceState(viewModel.bundle) and on onCreateView with scrollStateHolder = ScrollStateHolder(viewModel.bundle). Just replaced outBundle and savedInstanceState with those and it's working while changing fragment and/or rotation.
I used his ParentAdapter and ChildAdapter with his ScrollStateHolder modified with my models and views and it's working well. Later I'll try with other types of adapters and multi-views.
You could also try, a little more "ugly way" of doing it, create the layout managers that will be used in the child adapters in your fragment and past them when to their respective instances. Then, with the method described before, save and restore theor instance state. [not tested]
I'm new to Android development (and Kotlin).
I'm trying to implement a RecyclerView (which works fine) and when I click on a specific row it opens a new activity (Intent).
However, whenever I've press/click on one of the rows, I'm only able to get the value "-1" returned.
I've tried a number of different approaches (you should see the number of tabs in my browser).
This seems like it should be a fairly straightforward occurrence for something as common as a RecyclerView, but for whatever reason I'm unable to get it working.
Here is my RecyclerView Adapter file:
class PNHLePlayerAdapter (val players : ArrayList<PNHLePlayer>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
var onItemClick: ((Int)->Unit) = {}
// Gets the number of items in the list
override fun getItemCount(): Int {
return players.size
}
// Inflates the item views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(
R.layout.pnhle_list_item,
parent,
false
)
val viewHolder = ViewHolder(itemView)
itemView.setOnClickListener {
onItemClick(viewHolder.adapterPosition)
}
return ViewHolder(itemView)
}
// Binds each item in the ArrayList to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvPlayerName?.text = players[position].Name
holder.tvPlayerRank?.text = position.toString()
holder.tvPNHLe?.text = players[position].PNHLe.toString()
holder.tvTeam?.text = players[position].Team
holder.ivLeague?.setImageResource(leagueImageID)
}
}
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val linLayout = view.hor1LinearLayout
val ivTeam = view.teamImageView
val tvPlayerName = view.playerNameTextView
val tvPlayerRank = view.rankNumTextView
val tvPNHLe = view.pnhleTextView
val tvTeam = view.teamTextView
val ivLeague = view.leagueImageView
}
As you can see, there is a class property "onItemClick" which uses a lambda as the click callback.
I setOnClickListener in the onCreateViewHolder method after the view is inflated.
Next, in my Activity I add the list to my Adapter and set the call back.
However, every time I 'Toast' the position it is displayed as '-1'.
val adapter = PNHLePlayerAdapter(list, this)
adapter.onItemClick = { position ->
Toast.makeText(this, position.toString(),Toast.LENGTH_SHORT).show()
var intent = Intent(this, PlayerCardActivity::class.java)
//startActivity(intent)
}
rv_player_list.adapter = adapter
Perhaps I'm not thinking about this properly, but shouldn't the position represent the row number of the item out of the RecyclerView???
Ideally, I need to use the position so that I can obtain the correct item from the 'list' (ArrayList) so that I can pass information to my next Activity using the Intent
I found the issue.
Change this line in onCreateViewHolder:
return ViewHolder(itemView)
to this one:
return viewHolder
I would reorganize the adapter like this:
class PNHLePlayerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<Adapter.ViewHolder>() {
interface AdapterListener {
fun onItemSelected(position: Int?)
}
var players: List<Player> = listOf()
set(value) {
field = value
this.notifyDataSetChanged()
}
var listener: AdapterListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_car_selector, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
return brands.size
}
inner class ViewHolder(view: View): androidx.recyclerview.widget.RecyclerView.ViewHolder(view) {
private var position: Int? = null
private val baseView: LinearLayout? = view.findViewById(R.id.baseView) as LinearLayout?
...
init {
baseView?.setOnClickListener {
listener?.onManufacturerSelected(position)
}
}
fun bind(position: Int) {
this.position = position
...
}
}
}
And from your activity/fragment set the listener as adapter.listener = this, and implement the onItemSelected(position: Int?)
override fun onItemSelected(position: Int?) {
...
}
So I initially have 6 items in my RecyclerView. When I add an item I got a toast that's says that new item is added and shows that adapter items increased. But the new item is a duplicate of the item "6". Then I add to more items and ther are all "6". Then I scroll up and down and I see items "byButton"(that's the name of button added by clicking a button) that are between those duplicates of "6". And after a while the whole RecycleView reset and I still have 6 items just like at the beginning. I don't know what's wrong with my code.
class ConnectDeviceUI(val listAdapter: DeviceListAdapter): AnkoComponent<ConnectBleActivity> {
lateinit var addItemButton: FloatingActionButton
override fun createView(ui: AnkoContext<ConnectBleActivity>): View = with(ui) {
return relativeLayout() {
lparams(width= matchParent, height = matchParent)
textView("List of BLE devices"){
setTextAppearance(android.R.style.TextAppearance_Material_Large)
}
addItemButton = floatingActionButton {
imageResource = android.R.drawable.ic_input_add
}.lparams{
margin = dip(10)
alignParentBottom()
alignParentEnd()
alignParentRight()
gravity = Gravity.BOTTOM or Gravity.END
}
recyclerView(){
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
lparams(width= matchParent, height = matchParent)
adapter = listAdapter
}
}
}
}
class RowLayout(): AnkoComponent<ViewGroup>{
override fun createView(ui: AnkoContext<ViewGroup>): View = with(ui) {
return linearLayout(){
lparams(width= matchParent, height = wrapContent)
setPadding(0,dip(50),0,dip(50))
textView {
id = R.id.ble_item
setTextAppearance(android.R.style.TextAppearance_Material_Large)
setPadding(0,0,dip(100),0)
}.lparams(width = wrapContent, height = wrapContent )
button{
id = R.id.ble_item_button
}.lparams(width = wrapContent, height = wrapContent)
}
}
}
class ConnectBleActivity : AppCompatActivity(), AnkoLogger {
lateinit var BleDevicesList: ArrayList<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
BleDevicesList = arrayListOf("1", "2","3", "4", "5", "6")
var adapter = DeviceListAdapter(BleDevicesList)
var ui = ConnectDeviceUI(adapter)
ui.setContentView(this)
ui.addItemButton.onClick {
adapter.put()
toast("New item added. Number of items: ${adapter.itemCount} ")
}
}
}
class DeviceListAdapter(var deviceList: ArrayList<String>): RecyclerView.Adapter<DeviceListHolder>(){
override fun onBindViewHolder(holder: DeviceListHolder?, position: Int) {
holder?.bindItems(deviceList[position])
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): DeviceListHolder {
return DeviceListHolder(RowLayout().createView(AnkoContext.create(parent!!.context, parent)))
}
override fun getItemCount(): Int {
return deviceList.size
}
fun put(){
val randomString = UUID.randomUUID().toString()
deviceList.add(deviceList.lastIndex, "byButton")
notifyItemInserted(deviceList.lastIndex)
}
fun drop(){
deviceList.removeAt(deviceList.lastIndex)
notifyItemRemoved(deviceList.lastIndex)
}
}
class DeviceListHolder(var view: View): RecyclerView.ViewHolder(view){
val name: TextView = view.find(R.id.ble_item)
val bt_name: Button = view.find(R.id.ble_item_button)
fun bindItems(listItem: String){
name.text = listItem
bt_name.text = "Test"
}
}
Using List.lastIndex() after modifying the list itself, results in different values being updated in the model and the view
deviceList.add(deviceList.lastIndex, "byButton")
notifyItemInserted(deviceList.lastIndex)
The notification tells the RecyclerView to update the last item only. This contains a "6" while the old position is not updated at all. To resolve this issue, you have to use the same index for both.
val index = deviceList.lastIndex
deviceList.add(index, "byButton")
notifyItemInserted(index)