My approach to solve the problem of first and last item padding with Jetpack Compose
Tags: android, jetpackcompose
Published: 2022-02-06 17:43:15

**Edit: sorry for stretched screenshots. Please click on each to see it with correct ratio.**

In this post I'll share my approach to solve the problem I encounter quite often: a list of elements with equal spacing between items themselves as well as parent boundaries.

Here is an example of the view we are going to build:

![Items with equal spacing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nsdq9ag22wz3mx591yln.png)

To better visualize what I mean by _equal spacing_, I've highlighted these spaces:

![Spaces highlighted](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4y9oomn99krk8o47f1ew.png) 

---

First I'd like to explain **why the simplest approach will not work**. Here is the code of something I'd consider the simplest approach:

```kotlin
@Composable
fun ItemList() {
    Column(modifier = Modifier.padding(vertical = 8.dp)) {
        Item("Item 1")
        Item("Item 2")
        Item("Item 3")
        Item("Item 4")
    }
}

@Composable
fun Item(text: String) {
    Text(
        text = text,
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 8.dp)
    )
}

```
As you can see, `Item` has horizontal padding of `16.dp`. Vertical padding of `Item` and `Column` is `8.dp`. Vertical paddings are added so, as an result we get `16.dp` space everywhere.

This approach, however, has one big problem: **doesn't look pretty when clickable**. Let's modify `Item` function:

```kotlin
@Composable
fun Item(text: String) {
    Text(
        text = text,
        modifier = Modifier
            .fillMaxWidth()
            .clickable {  } // Add this line
            .padding(horizontal = 16.dp, vertical = 8.dp)
    )
}
```
Now, try to click on first item:

![First item clicked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iiaipw75vxhpe9rov1fl.png)

As you can see, there is a white space above the clicked item. It is ugly. To fix it, we have to migrate from the padding of parent (`Column`) to the padding of child (`Item`).

This mean that first and last items should have different vertical padding than central ones. `top` padding of first item should be `16.dp`, the same apply for `bottom` padding of the last item. Other paddings stay unchanged with the value of `8.dp`.

There are several possible approaches. We can pass `index` as an argument to `Item` function. We can pass `isFirst` and `isLast` flags. We can pass padding value directly. All these approach are valid, however _my personal sense of taste_ consider them not elegant.

Therefore I'd suggest another approach: **pass the `Modifier` as an argument**.

Here is the final solution:

```kotlin

@Composable
fun ItemList() {
    Column {
        Item("Item 1", modifier = Modifier.padding(top = 16.dp, bottom = 8.dp))
        Item("Item 2")
        Item("Item 3")
        Item("Item 4", modifier = Modifier.padding(top = 8.dp, bottom = 16.dp))
    }
}

@Composable
fun Item(text: String, modifier: Modifier = Modifier.padding(vertical = 8.dp)) {
    Text(
        text = text,
        modifier = Modifier
            .fillMaxWidth()
            .clickable {  }
            .padding(horizontal = 16.dp)
            .then(modifier)
    )
}
```

And the result (with first item clicked) looks like the following:

![First item clicked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jnk00pm1xhi6wx9tz3zn.png)

---

What is important here, I've used `.then(modifier)` option. I encourage you to try out what happen if instead of using `then`, modifier is injected in this way:

```kotlin
modifier = modifier
    .fillMaxWidth()
    .clickable {  }
    .padding(horizontal = 16.dp)
```

Spoiler: because vertical padding would be applied before applying `clickable`, the on-click highlighted area will not cover padding. 
That is why the order of modifiers is so important and why `then` function is useful.

## (Optional) further refactoring

I believe it is very important not to mix abstraction levels in the code. The following code is an example of mixing abstractions:

```kotlin
// Padding size is set in the parent function:
Item("Item 1", modifier = Modifier.padding(top = 16.dp, bottom = 8.dp))
// Padding size is set in the child function:
Item("Item 2")
```

Therefore I suggest the following steps of refactoring:

First, pull the padding values to the parent:

```kotlin
@Composable
fun ItemList() {
    Column {
        Item("Item 1", modifier = Modifier.padding(top = 16.dp, bottom = 8.dp))
        Item("Item 2", modifier = Modifier.padding(vertical = 8.dp))
        Item("Item 3", modifier = Modifier.padding(vertical = 8.dp))
        Item("Item 4", modifier = Modifier.padding(top = 8.dp, bottom = 16.dp))
    }
}
```

Then create helper functions to increase readability and remove code duplication:

```kotlin
@Stable
private fun Modifier.firstElementPadding(padding: Dp) =
    padding(top = padding, bottom = padding / 2)

@Stable
private fun Modifier.middleElementPadding(padding: Dp) =
    padding(vertical = padding / 2)

@Stable
private fun Modifier.lastElementPadding(padding: Dp) =
    padding(top = padding / 2, bottom = padding)
```
```kotlin
Item("Item 1", modifier = Modifier.firstElementPadding(16.dp))
Item("Item 2", modifier = Modifier.middleElementPadding(16.dp))
Item("Item 3", modifier = Modifier.middleElementPadding(16.dp))
Item("Item 4", modifier = Modifier.lastElementPadding(16.dp))
```