Jetpack Compose has revolutionized Android UI development with its declarative approach, enabling developers to build beautiful and efficient user interfaces with minimal code.
However, when it comes to displaying large lists of data, developers often encounter performance issues with the built-in LazyLists.
ComposeRecyclerView comes to the rescue as a library that harnesses the power of RecyclerView within Jetpack Compose.
Empower your journey to well-being with Justly. Your life is your greatest wealth — let us be your guide on the path to a happier you!
In this post, we’ll delve into the implementation of ComposeRecyclerView, exploring its architecture, essential components, and inner workings.
ComposeRecyclerView is available on Github and MavenCentral
Check out a sample implementation of the ComposeRecyclerView library on GitHub, where you can find usage examples and see how it simplifies the RecyclerView integration with Jetpack Compose.
At its core, ComposeRecyclerView revolves around the familiar RecyclerView component, enhanced with Jetpack Compose’s composables. Let’s dissect its key components:
The ComposeRecyclerView
composable function serves as the backbone of the ComposeRecyclerView library, facilitating the creation of RecyclerViews with dynamically generated Compose items.
Let's delve into the implementation details of each parameter and callback:
@Composable
fun ComposeRecyclerView(
modifier: Modifier = Modifier,
itemCount: Int,
itemBuilder: @Composable (index: Int) -> Unit,
onScrollEnd: () -> Unit = {},
orientation: LayoutOrientation = LayoutOrientation.Vertical,
itemTypeBuilder: ComposeRecyclerViewAdapter.ItemTypeBuilder? = null,
onDragCompleted: (position: Int) -> Unit = { _ -> },
itemTouchHelperConfig: (ItemTouchHelperConfig.() -> Unit)? = null,
onItemMove: (fromPosition: Int, toPosition: Int, itemType: Int) -> Unit = { _, _, _ -> },
onCreate: (RecyclerView) -> Unit = {}
) {
// Implementation...
}
Modifier
that allows customization of the RecyclerView's appearance and behavior.var scrollState by rememberSaveable { mutableStateOf(bundleOf()) }
scrollState
, using rememberSaveable
, which preserves scroll value across recompositions.val layoutManager = remember {
val layoutManager = LinearLayoutManager(context)
layoutManager.onRestoreInstanceState(scrollState.getParcelable("RecyclerviewState"))
// Layout manager configuration based on orientation...
layoutManager.orientation = when (orientation) {
LayoutOrientation.Horizontal -> RecyclerView.HORIZONTAL
LayoutOrientation.Vertical -> RecyclerView.VERTICAL
}
layoutManager
}
LinearLayoutManager
.val adapter = remember {
ComposeRecyclerViewAdapter().apply {
this.totalItems = itemCount
this.itemBuilder = itemBuilder
itemTypeBuilder?.let {
this.itemTypeBuilder = itemTypeBuilder
}
this.layoutOrientation = orientation
}
}
ComposeRecyclerViewAdapter
.val composeRecyclerView = remember {
RecyclerView(context).apply {
this.layoutManager = layoutManager
this.clipToOutline = true
addOnScrollListener(object : InfiniteScrollListener() {
override fun onScrollEnd() {
onScrollEnd()
}
})
this.adapter = adapter
}
}
RecyclerView
.val config = remember {
ItemTouchHelperConfig().apply { itemTouchHelperConfig?.invoke(this) }
}
val itemTouchHelper = remember {
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
config.dragDirs ?: (UP or DOWN or START or END), config.swipeDirs ?: (LEFT or RIGHT)
) {
// Callbacks for drag and swipe interactions...
})
}
config, itemTouchHelper
variables using remember
, ensuring that its state is preserved across recompositions.ItemTouchHelperConfig
object and applies any specified configurations through the lambda itemTouchHelperConfig
.config
object's properties, with defaults set to allow movement in all directions for drag and swipe.AndroidView(
factory = {
composeRecyclerView.apply {
onCreate.invoke(this)
itemTypeBuilder?.let {
itemTouchHelper.attachToRecyclerView(this)
}
}
},
modifier = modifier,
update = {
adapter.update(itemCount, itemBuilder, orientation, itemTypeBuilder)
}
)
AndroidView
.onCreate
callback.itemTouchHelper
to the RecyclerView if itemTypeBuilder
is provided, enabling drag-and-drop functionality.DisposableEffect(key1 = Unit, effect = {
onDispose {
scrollState = bundleOf("RecyclerviewState" to layoutManager.onSaveInstanceState())
}
})
onSaveInstanceState()
to preserve its position across configuration changes.The final code is available on github!
It plays a pivotal role in managing dynamically generated Compose items within RecyclerViews.
Acting as a bridge between RecyclerViews and Compose, offering a streamlined approach to managing dynamically generated Compose items within RecyclerViews.
This adapter simplifies the process of populating RecyclerViews with Compose content, enabling developers to create highly customizable and performant UIs.
In this section, we’ll dive into the detailed implementation of it, exploring its key features and functionalities.
/**
* RecyclerView adapter for handling dynamically generated Compose items.
*/
class ComposeRecyclerViewAdapter :
RecyclerView.Adapter<ComposeRecyclerViewAdapter.ComposeRecyclerViewHolder>(){
// Interface to define the method for determining item type
interface ItemTypeBuilder {
fun getItemType(position: Int): Int
}
// List to hold the items in the RecyclerView
private var itemList: MutableList<Any> = mutableListOf()
// Total number of items in the RecyclerView
var totalItems: Int = 0
set(value) {
if (field == value) return
field = value
// Notify the adapter about changes in item count
notifyItemRangeChange(value)
}
// Lambda function to build each item in the RecyclerView
var itemBuilder: (@Composable (index: Int) -> Unit)? =
null
// Interface implementation to determine item type
var itemTypeBuilder: ItemTypeBuilder? = null
// Layout orientation of the RecyclerView
var layoutOrientation: LayoutOrientation = LayoutOrientation.Vertical
set(value) {
if (field == value) return
field = value
// Notify the adapter about changes in layout orientation
notifyItemChanged(0)
}
// ViewHolder class to hold ComposeView
inner class ComposeRecyclerViewHolder(val composeView: ComposeView) :
RecyclerView.ViewHolder(composeView)
// Called when RecyclerView needs a new ViewHolder of the given type to represent an item
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ComposeRecyclerViewHolder {
val context = parent.context
val composeView = ComposeView(context)
return ComposeRecyclerViewHolder(composeView)
}
// Called by RecyclerView to display the data at the specified position
override fun onBindViewHolder(holder: ComposeRecyclerViewHolder, position: Int) {
holder.composeView.apply {
tag = holder
// Set the content of the ComposeView using the provided lambda function itemBuilder
setContent {
itemBuilder?.invoke(position)
}
}
}
// Returns the total number of items held by the adapter
override fun getItemCount(): Int = totalItems
// Returns the view type of the item at the specified position
override fun getItemViewType(position: Int): Int {
return itemTypeBuilder?.getItemType(position) ?: 0
}
// Notifies the adapter about changes in item count with optimized range change notifications
private fun notifyItemRangeChange(newSize: Int) {
val oldSize = itemList.size
if (newSize < oldSize) {
// Remove excess items from the list and notify corresponding range removed
itemList = itemList.subList(0, newSize)
notifyItemRangeRemoved(newSize, oldSize - newSize)
} else if (newSize > oldSize) {
// Add new items to the list and notify corresponding range inserted
val list = MutableList(newSize - oldSize) { Any() }
itemList = (itemList + list).toMutableList()
notifyItemRangeInserted(oldSize, newSize - oldSize)
}
}
}
The final code is available on github!
This class empowers developers with the ability to finely tune the behavior of drag-and-drop and swipe actions within a ComposeRecyclerView
.
With its set of configurable callbacks and flags, it offers a versatile solution for tailoring the user interaction experience to meet specific application requirements.
Let's delve into its key features:
class ItemTouchHelperConfig {
var nonDraggableItemTypes: Set<Int> = emptySet()
var onMove: ((recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) -> Boolean)? = null
var onSwiped: ((viewHolder: RecyclerView.ViewHolder, direction: Int) -> Unit)? = null
var clearView: ((recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) -> Unit)? = null
var getMovementFlags: ((recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) -> Int)? = null
var onSelectedChanged: ((viewHolder: RecyclerView.ViewHolder?, actionState: Int) -> Unit)? = null
var isLongPressDragEnabled: Boolean = true
var swipeDirs: Int? = null
var dragDirs: Int? = null
}
Utilizing the capabilities of the ItemTouchHelperConfig
, developers can craft immersive and intuitive user experiences that seamlessly align with their app’s design and functionality.
Whether implementing intricate drag-and-drop gestures or fine-tuning swipe-to-dismiss actions, this configuration class empowers developers with the tools to unlock endless possibilities for interaction customization.
Ready to elevate your Jetpack Compose apps with high-performance RecyclerViews?
Get started with ComposeRecyclerView today by integrating it into your project and exploring its rich feature set. Experience the difference in performance and user interaction that ComposeRecyclerView brings to your Android app development workflow.
Stay tuned for updates, enhancements, and new features as we continue to evolve ComposeRecyclerView to meet the needs of modern Android app development.
Happy coding! 🚀
ComposeRecyclerView emerges as a powerful solution, delivering heightened performance, drag-to-reorder functionality, and effortless integration with Jetpack Compose.
Whether you’re building a social media feed, a task manager, or a product catalog, ComposeRecyclerView empowers developers to deliver fluid and responsive user experiences.
Get started today
Let's build the next
big thing!
Let's improve your business's digital strategy and implement robust mobile apps to achieve your business objectives. Schedule Your Free Consultation Now.
Get Free ConsultationGet started today
Let's build the next big thing!
Let's improve your business's digital strategy and implement robust mobile apps to achieve your business objectives. Schedule Your Free Consultation Now.
Get Free Consultation