3

When creating a LazyColumn layout, is there a way to modify the items source (i.e. Array<Starters>) to something else so that I can reuse my LazyColumn for a different set of items from a different array?

@Composable
fun MyLazyColumn(lazyItems: Array<Starters>,
                  onClickItem: (Starters) -> Unit
) {
    LazyColumn() {
        items(lazyItems) { choice -> Row() { Text(text = stringResource(id = choice.textResId)) } }
    }
}

Scaffold(
    content = {
        MyLazyColumn(lazyItems = arrayOf(Starters.Canapes,...), onClickItem = startersClickListner)
    }
)
wbk727
  • 8,017
  • 12
  • 61
  • 125

2 Answers2

1

You can create a generic lazyColumn by giving the opportunity for the caller to use the item composable that he want with a @Composable function callback.

Exemple :

@Composable
fun <T> MyLazyColumn(
    lazyItems: Array<T>,
    onClickItem: (T) -> Unit,
    item: @Composable RowScope.(item: T) -> Unit,
) {
    LazyColumn() {
        items(lazyItems) { choice ->
            Row(
                modifier = Modifier.clickable(onClick = { onClickItem(choice) })
            ) {
                item(choice)
            }
        }
    }
}

In your scaffold :

    Scaffold(
        content = {
            MyLazyColumn<Starters>(
                lazyItems = arrayOf(Starters.Canapes, ...),
                onClickItem = startersClickListner
            ) {
                Text(text = stringResource(it.textResId) )
            }
        }
    )

If you want to use a custom key you can add another function parameter

@Composable
fun <T> MyLazyColumn(
    lazyItems: Array<T>,
    onClickItem: (T) -> Unit,
    key: ((item: T) -> Any)? = null,
    item: @Composable RowScope.(item: T) -> Unit
) {
    LazyColumn() {
        items(
            items = lazyItems,
            key = key
        ) { choice ->
            Row(
                modifier = Modifier.clickable(onClick = { onClickItem(choice) })
            ) {
                item(choice)
            }
        }
    }
}

And on the caller side, you can use it like this

data class Foo(
    val id: Int
)

MyLazyColumn(
        lazyItems = arrayOf(
            Foo(1), Foo(2)
        ),
        key = {
            it.id
        },
        onClickItem = {
            // ...
        }
    ) {
        // ...
    }
Jolan DAUMAS
  • 1,078
  • 1
  • 5
  • 9
  • Don't you need to use keys with LazyColumn? How can you get the id value from the generic `T`? `T.id` won't work and will throw an Unresolved reference error. – Raj Narayanan Jan 13 '23 at 23:00
  • Firstly, You don't necessarily need to use a key inside a LazyColumn. According to documentation https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListScope#items(kotlin.Int,kotlin.Function1,kotlin.Function1,kotlin.Function2 by default it will use the position of the item as key. Secondly, If you want to have less abstraction you can replace T by any type you want and when you will use the composable, the item will need to extend the type you choosed – Jolan DAUMAS Jan 14 '23 at 21:14
  • I edited my answer to show an example on how to define a custom key – Jolan DAUMAS Jan 14 '23 at 21:44
  • Thanks for updating your answer. I'm assuming that you're passing back the item T back to the caller via the key lambda, but how are you then setting the the `id` key value at the calling site? – Raj Narayanan Jan 14 '23 at 23:48
  • I Updated my answer. the key function parameter allow you on caller side to use any attributes of the class provided. Here I created a Foo data class which has an id attributes that I can use in the key function parameter – Jolan DAUMAS Jan 15 '23 at 10:10
  • Great. Just one more thing. When you read the `id` value at the calling site in the lambda callback via `it.id`, is it implicitly setting the item key in the `LazyColumn` in the `MyLazyColumn` composable? – Raj Narayanan Jan 15 '23 at 14:39
  • Exactly because the function is used by the items key lambda so you are setting it directly on the LazyColumn – Jolan DAUMAS Jan 15 '23 at 15:13
  • I want to make the custom `LazyColumn` composable even more reusable by specifying a more generic type for `lazyItems` with `Collection` so that we can pass in any collection type for the items like `List`, `Set`, or `Map`. But specifying `Collection` throws an error in the `items` DSL method stating it requires `Int` but found `Collection`. How can I make this work? Thanks. – Raj Narayanan Jan 24 '23 at 00:12
  • The issue here is that you forgot the following import : import androidx.paging.compose.items – Jolan DAUMAS Jan 24 '23 at 08:59
0

The answer given by Dinamots is probably the most "Compose" way to go about it. However, if all the things you want to give to the column have a common parent or interface they implement, you can do something like this to get a little more code reuse.

interface Choice {
    val textResId: Int
}

@Composable
fun <T : Choice> ChoiceColumn(
    choices: Array<T>,
    onChoiceClick: ((T) -> Unit) = { }
) {
    BasicLazyColumn(choices, onChoiceClick) {
        Text(stringResource(it.textResId))
    }
}

@Composable
fun <T> BasicLazyColumn(
    choices: Array<T>,
    onItemClick: ((T) -> Unit) = { },
    itemBuilder: @Composable LazyItemScope.(item: T) -> Unit
) {
    LazyColumn {
        items(choices) { choice ->
            Row(Modifier.clickable {
                onItemClick(choice)
            }) {
                itemBuilder(choice)
            }
        }
    }
}
nEx.Software
  • 6,782
  • 1
  • 26
  • 34