Jetpack Compose Paging With Header

December 14, 2021

Modify sample code from Jetpack Compose Paging Sample.

Modify MemberRepository to generate age in sequence.

class MemberRepository(val totalCount: Int, val pageSize: Int) {

    suspend fun getMembers(page: Int): List<Member> {
        val random = Random(page)

        val startIndex = (page - 1) * pageSize + 1
        var endIndex = startIndex + pageSize - 1
        if (endIndex > totalCount) {
            endIndex = totalCount
        }

        return  (startIndex..endIndex).map { index ->
            // Member(name = "Member #${index}", age = random.nextInt(1, 99))
            Member(name = "Member #${index}", age = index)
        }
    }
}

Sample using Sticky Header

@ExperimentalFoundationApi
@Composable
fun MemberScreen(viewModel: MemberViewModel = viewModel()) {
    val lazyMembers = viewModel.membersFlow.collectAsLazyPagingItems()

    LazyColumn {
        val itemCount = lazyMembers.itemCount
        var lastAgeGroup: Int? = null

        for (index in 0 until itemCount) {

            val nextMember = lazyMembers.peek(index)
            if (nextMember != null) {
                val age = nextMember.age
                // create a header for every 10y of age
                if (lastAgeGroup == null || age > lastAgeGroup) {
                    lastAgeGroup = (lastAgeGroup ?: 0) + 10
                    val ageGroup = lastAgeGroup
                    stickyHeader(key = ageGroup) {
                        Text("<= $ageGroup")
                    }
                }
            }

            val member = lazyMembers[index]
            if (member != null) {
                item(key = member.name) {
                    Text("${member.name}, age=${member.age}")
                }
            }
        }
    }
}

Sample using Text as header.

@ExperimentalFoundationApi
@Composable
fun MemberScreen(viewModel: MemberViewModel = viewModel()) {
    val lazyMembers = viewModel.membersFlow.collectAsLazyPagingItems()

    LazyColumn {
        val itemCount = lazyMembers.itemCount
        var lastAgeGroup: Int? = null

        for (index in 0 until itemCount) {

            val nextMember = lazyMembers.peek(index)
            var ageGroup: Int? = null
            if (nextMember != null) {
                val age = nextMember.age
                if (lastAgeGroup == null || age > lastAgeGroup) {
                    lastAgeGroup = (lastAgeGroup ?: 0) + 10
                    ageGroup = lastAgeGroup

                }
            }

            val member = lazyMembers[index]
            if (member != null) {
                item(key = member.name) {
                    if (ageGroup != null) {
                        Text("< $ageGroup")
                    }
                    Text("${member.name}, age=${member.age}")
                }
            }
        }
    }
}

Alternatively, you can also group data together before render them.

@ExperimentalFoundationApi
@Composable
fun MemberScreen(viewModel: MemberViewModel = viewModel()) {
    val lazyMembers = viewModel.membersFlow.collectAsLazyPagingItems()


    val memberGroup = mutableMapOf<String, MutableList<Int>>()
    val itemCount = lazyMembers.itemCount
    var lastAgeGroup: Int? = null

    // to fill up memberGroup, will not trigger any render
    for (index in 0 until itemCount) {
        val nextMember = lazyMembers.peek(index)
        var ageGroup: Int? = null
        if (nextMember != null) {
            val age = nextMember.age
            if (lastAgeGroup == null || age > lastAgeGroup) {
                lastAgeGroup = (lastAgeGroup ?: 0) + 10
                ageGroup = lastAgeGroup
                memberGroup[lastAgeGroup] = mutableListOf()
            }

            memberGroup[lastAgeGroup]?.add(index)
        }
    }

    // actual render
    for ((ageGroup, memberIndexes) in memberGroup) {
        stickyHeader(key = ageGroup) {
            Text("< $ageGroup")
        }


        // you can group render into single item(...), or use individual item(...) for each
        item(key = "$ageGroup-members") {
            for (index in memberIndexes) {
                // this will trigger pageload
                val member = lazyMembers[index]
                Column {
                    Text("${member.name}, age=${member.age}")
                }
            }   
        }
    }
}
This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.