Skip to content

Commit

Permalink
Fix invalid view holder adapter position exception (#11)
Browse files Browse the repository at this point in the history
* Fix item delete crash
---------

Co-authored-by: Jimmy S <[email protected]>
  • Loading branch information
cp-megh-l and jimmy0251 authored Feb 20, 2024
1 parent d62c935 commit 801f238
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import com.example.compose_recyclerview.utils.ItemTouchHelperConfig
* Composable function to display a RecyclerView with dynamically generated Compose items.
*
* @param modifier The modifier to be applied to the RecyclerView.
* @param itemCount The total number of items to be displayed in the RecyclerView.
* @param items The list of items to be displayed in the RecyclerView.
* @param itemBuilder The lambda function responsible for creating the Compose content for each item at the specified index.
* @param onScrollEnd Callback triggered when the user reaches the end of the list during scrolling.
* @param orientation The layout direction of the RecyclerView.
Expand All @@ -43,10 +43,10 @@ import com.example.compose_recyclerview.utils.ItemTouchHelperConfig
* @param onCreate Callback to customize the RecyclerView after its creation.
*/
@Composable
fun ComposeRecyclerView(
fun <T> ComposeRecyclerView(
modifier: Modifier = Modifier,
itemCount: Int,
itemBuilder: @Composable (index: Int) -> Unit,
items: List<T>,
itemBuilder: @Composable (item: T, index: Int) -> Unit,
onScrollEnd: () -> Unit = {},
orientation: LayoutOrientation = LayoutOrientation.Vertical,
itemTypeBuilder: ComposeRecyclerViewAdapter.ItemTypeBuilder? = null,
Expand All @@ -69,13 +69,14 @@ fun ComposeRecyclerView(
}

val adapter = remember {
ComposeRecyclerViewAdapter().apply {
this.totalItems = itemCount
ComposeRecyclerViewAdapter<T>().apply {
this.itemList = items
this.itemBuilder = itemBuilder
itemTypeBuilder?.let {
this.itemTypeBuilder = itemTypeBuilder
}
this.layoutOrientation = orientation
this.layoutManager = layoutManager
}
}

Expand Down Expand Up @@ -119,7 +120,7 @@ fun ComposeRecyclerView(
target.bindingAdapterPosition,
fromType
)
(recyclerView.adapter as ComposeRecyclerViewAdapter).onItemMove(
(recyclerView.adapter as ComposeRecyclerViewAdapter<*>).onItemMove(
viewHolder.bindingAdapterPosition,
target.bindingAdapterPosition
)
Expand Down Expand Up @@ -183,7 +184,7 @@ fun ComposeRecyclerView(
},
modifier = modifier,
update = {
adapter.update(itemCount, itemBuilder, orientation, itemTypeBuilder)
adapter.update(items, itemBuilder, orientation, itemTypeBuilder)
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,30 @@ package com.example.compose_recyclerview.adapter
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.compose_recyclerview.data.LayoutOrientation
import kotlin.math.max

/**
* RecyclerView adapter for handling dynamically generated Compose items.
*/
class ComposeRecyclerViewAdapter :
RecyclerView.Adapter<ComposeRecyclerViewAdapter.ComposeRecyclerViewHolder>(){
class ComposeRecyclerViewAdapter<T> :
RecyclerView.Adapter<ComposeRecyclerViewAdapter<T>.ComposeRecyclerViewHolder>() {

interface ItemTypeBuilder {
fun getItemType(position: Int): Int
}

var totalItems: Int = 0
var itemList: List<T> = mutableListOf()
set(value) {
if (field == value) return
val old = field
field = value
if (field == -1) {
notifyItemInserted(0)
} else {
notifyItemChanged(0)
}
notifyItemRangeChange(old)
}

var itemBuilder: (@Composable (index: Int) -> Unit)? =
var itemBuilder: (@Composable (item: T, index: Int) -> Unit)? =
null

var itemTypeBuilder: ItemTypeBuilder? = null
Expand All @@ -39,6 +38,8 @@ class ComposeRecyclerViewAdapter :
notifyItemChanged(0)
}

var layoutManager: LinearLayoutManager? = null

inner class ComposeRecyclerViewHolder(val composeView: ComposeView) :
RecyclerView.ViewHolder(composeView)

Expand All @@ -52,12 +53,14 @@ class ComposeRecyclerViewAdapter :
holder.composeView.apply {
tag = holder
setContent {
itemBuilder?.invoke(position)
if (position < itemList.size) {
itemBuilder?.invoke(itemList[position], position)
}
}
}
}

override fun getItemCount(): Int = totalItems
override fun getItemCount(): Int = itemList.size

override fun getItemViewType(position: Int): Int {
return itemTypeBuilder?.getItemType(position) ?: 0
Expand All @@ -68,16 +71,29 @@ class ComposeRecyclerViewAdapter :
}

fun update(
itemCount: Int,
itemBuilder: @Composable (index: Int) -> Unit,
items: List<T>,
itemBuilder: @Composable (item: T, index: Int) -> Unit,
layoutOrientation: LayoutOrientation,
itemTypeBuilder: ItemTypeBuilder?
) {
this.totalItems = itemCount
this.itemList = items
this.itemBuilder = itemBuilder
this.layoutOrientation = layoutOrientation
itemTypeBuilder?.let {
this.itemTypeBuilder = it
}
}

private fun notifyItemRangeChange(oldItems: List<T>) {
val oldSize = oldItems.size
val newSize = itemList.size
val firstVisibleIndex = layoutManager?.findFirstVisibleItemPosition() ?: 0
if (newSize < oldSize) {
val position = max(0, firstVisibleIndex)
notifyItemRangeRemoved(position, oldSize - newSize)
} else if (newSize > oldSize) {
val start = max(0, firstVisibleIndex)
notifyItemRangeInserted(start, newSize - oldSize)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ class MainActivity : ComponentActivity() {

ComposeRecyclerView(
modifier = Modifier.fillMaxSize(),
itemCount = 1 + userDataList.size + 1 + otherUsersDataList.size,
itemBuilder = { index ->
items = listOf(1) + userDataList + listOf(1) + otherUsersDataList,
itemBuilder = { item, index ->
if (index == 0) {
Box(
modifier = Modifier.fillMaxWidth(),
Expand Down

0 comments on commit 801f238

Please sign in to comment.