RecyclerView and LazyColumnFor in Jetpack Compose
Tags: Mobile App Development, Android App Development, AndroidDev, Android, Jetpack Compose

Learning Android Development

RecyclerView and LazyColumnFor in Jetpack Compose

Learn The RecyclerView of Jetpack Compose

Photo by Martin Sanchez on Unsplash

Most of the Apps if not all, will need to store a list of items to be viewed. RecyclerView is the key view one cannot go without in developing a professional App.

As we are transitioning to Jetpack Compose, the ability to display a list of items is most important. Hence this article looks into both approach and share them.

RecyclerView

This is presented for comparison with LazyColumnFor . If you already familiar with RecyclerView, you can skip to the next section.

In conventional Android Development, we will need to have XML to define the layout view. For RecyclerView, we’ll need to have at least 2 layouts, one is the Activity layout that contains the RecyclerView, and the other the Item Layout for the individual ViewHolder.

On top of that, to access the XMLs, we’ll have the Activity Class and the ViewHoldre class, which are connected through the RecyclerView.Adaptor class.

The XMLs

The Activity.xml contains just the RecyclerView inside

<androidx.recyclerview.widget.RecyclerView

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/recycler_view"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".RecyclerViewActivity" />

The Item.xml defined the layout of individual

<androidx.cardview.widget.CardView

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:card_view="http://schemas.android.com/apk/res-auto"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

card_view:cardBackgroundColor="#ccc"

card_view:cardCornerRadius="4dp">



<LinearLayout

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:orientation="horizontal">



<TextView

android:id="@+id/item_number"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:padding="8dp"

android:textAlignment="center"

android:textColor="#f00"

android:textSize="24sp" />



<TextView

android:id="@+id/item_country"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:padding="8dp"

android:textAlignment="center"

android:textColor="@color/black"

android:textSize="24sp" />

</LinearLayout>

</androidx.cardview.widget.CardView>

The classes

We are using View Binding, and bind the Activity to the XML as below

class RecyclerViewActivity : AppCompatActivity() {



private lateinit var binding: ActivityMainBinding



override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

binding = ActivityMainBinding.inflate(layoutInflater)

setContentView(binding.root)

val listItems = intent.getSerializableExtra(

LauncherActivity.DATA_KEY) as List<Item>

binding.recyclerView.apply {

layoutManager = LinearLayoutManager(

this@RecyclerViewActivity)

adapter = RecyclerViewAdapter(listItems)

addItemDecoration(MarginItemDecoration(

resources.getDimension(

R.dimen.default_padding).toInt()))

}

}

}

In order to connect the ViewHolder, we need to have the RecyclerView.Adaptor

class RecyclerViewAdapter(private val listItems: List<Item>)

: RecyclerView.Adapter<ItemViewHolder>() {



override fun onCreateViewHolder(

parent: ViewGroup, viewType: Int): ItemViewHolder {

val binding = ViewItemBinding.inflate(

LayoutInflater.from(parent.context), parent, false

)

return ItemViewHolder(binding)

}



override fun onBindViewHolder(

holder: ItemViewHolder, position: Int) {

holder.bindView(listItems[position])

}



override fun getItemCount(): Int {

return listItems.size

}

}

Lastly, the ViewHolder, which bind to the XML

class ItemViewHolder(private val viewBinding: ViewItemBinding)

: RecyclerView.ViewHolder(viewBinding.root) {

fun bindView(content: Item) {

viewBinding.itemNumber.text = content.number

viewBinding.itemCountry.text = content.country

}

}

Quite a bit of classes to code. The summary diagram can be seen below.

You’ll see a disconnect of the two XMLs below, which are connected through the RecyclerView.Adaptor.

LazyColumnFor

In JetpackCompose, we no longer need to have XML. Instead, we replace both the class and XML with composable functions.

So nice, no more XML, and we see the direct connection of the LazyColumnFor with the ViewItem directly without the need of any Adaptor.

If we look at the code

The Activity

class LazyColumnActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

val listItems =

intent.getSerializableExtra(DATA_KEY) as List<Item>

setContent {

MyLazyColumn(

listItems = listItems

)

}

}

}

The Lazy Column

@Composable

fun MyLazyColumn(

modifier: Modifier = Modifier,

listItems: List<Item>,

) {

LazyColumnFor(

modifier = modifier, items = listItems,

contentPadding = PaddingValues(16.dp, 16.dp, 16.dp)

) { itemText ->

ViewItem(itemText = itemText)

Spacer(modifier = Modifier.fillMaxWidth().height(16.dp))

}

}

The View Item

@Composable

fun ViewItem(

itemText: Item

) {

Card(

shape = RoundedCornerShape(4.dp),

backgroundColor = Color(0xFFCCCCCC),

) {

Row {

Text(

text = itemText.number,

modifier = Modifier.padding(8.dp),

style = TextStyle(

fontSize = 24.sp, color = Color.Red),

textAlign = TextAlign.Center

)

Text(

text = itemText.country,

modifier = Modifier.padding(8.dp),

style = TextStyle(

fontSize = 24.sp, color = Color.Black),

textAlign = TextAlign.Center

)

}

}

}

Here, we can see so much simpler and directly related.

Margin between the View Items

In RecyclerView, in order to have a proper margin between the view, we’ll need ItemDecoration for that purpose.

ItemDecoration

In our RecyclerView, to ensure proper margin, we have the below ItemDecoration where for the first view item, we’ll have the Intop margin, while the others will only have the left, right, and bottom margin.

class MarginItemDecoration(private val spaceHeight: Int) :

RecyclerView.ItemDecoration() {

override fun getItemOffsets(

outRect: Rect, view: View,

parent: RecyclerView, state: RecyclerView.State) {

with(outRect) {

if (parent.getChildAdapterPosition(view) == 0) {

top = spaceHeight

}

left = spaceHeight

right = spaceHeight

bottom = spaceHeight

}

}

}

Notice only the first item has a top margin

Workaround for LazyColumnFor

I cannot find the equivalent of ItemDecoration for LazyColumnFor in Jetpack Compose. I have posted a StackOverflow inquiry but got no answer other than my workaround below.

The workaround doesn’t seems bad. We can add contentPadding to the LazyColumnFor , which each of the item, we add a Spacer as the padding.

@Composable

fun MyLazyColumn(

modifier: Modifier = Modifier,

listItems: List<Item>,

) {

LazyColumnFor(

modifier = modifier, items = listItems,

contentPadding = PaddingValues(16.dp, 16.dp, 16.dp)

) { itemText ->

ViewItem(itemText = itemText)

Spacer(modifier = Modifier.fillMaxWidth().height(16.dp))

}

}

The top left and right margin are the LazyColumnFor padding

Other comparisons

Overscroll Indication

In normal RecyclerView, when one over-scroll the list, there will be some indication shows.

However, this is not readily available for LazyColumnFor . Perhaps one day later, or I’m not aware of it.

Scrolling State

In Android, sometimes the app will be killed by the system and get restored then the user return to the App. We can emulate this behavior using Don’t Keep Activity.

For simple RecyclerView and LazyColumnFor, based on the experiment, it seems the scroll position state is automatically saved and restored. Hence no extra coding required.