When trying to put a LazyVerticalGrid inside a scrollable Column I get the following error:
java.lang.IllegalStateException: Nesting scrollable in the same
direction layouts like LazyColumn and
Column(Modifier.verticalScroll()) is not allowed. If you want to add a
header before the list of items please take a look on LazyColumn
component which has a DSL api which allows to first add a header via
item() function and then the list of items via items().
I am not making a traditional list, I just have alot of elements that are too big to fit on the screen. Therefore I want the column to scroll so I can see all the elements. Here is my code:
#ExperimentalFoundationApi
#Composable
fun ProfileComposable(id: String?) {
val viewModel: ProfileViewModel = viewModel()
if (id != null) {
viewModel.getProfile(id)
val profile = viewModel.profile.value
val scrollState = rememberScrollState()
if (profile != null) {
Column(modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.verticalScroll(scrollState)) {
Row() {
ProfilePic(profile.getImgUrl(), profile.name)
Column(Modifier.padding(16.dp)) {
ProfileName(profile.name)
Stats(profile.stats) // <--------------- the offending composable
}
}
Sprites(sprites = profile.sprites)
TextStat(profile.id.toString(), "Pokemon Number")
TextStat(profile.species.name, "Species")
TextStat(profile.types.joinToString { it.type.name }, "Types")
TextStat(profile.weight.toString(), "Weight")
TextStat(profile.forms.joinToString { it.name }, "Forms")
}
} else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
} else {
Text("Error")
}
}
The Stats() composable contains the LazyVerticalGrid which causes the error:
#ExperimentalFoundationApi
#Composable
fun Stats(stats: List<Stat>) {
LazyVerticalGrid(cells = GridCells.Fixed(2)) {
itemsIndexed(stats) { index, item ->
StatBox(stat = item)
}
}
}
I do not want the grid to scroll, I just want to display a grid within a scrollable column.
I had a similar use-case, the goal was to design a profile screen that has a bunch of information and statistics on top, and then comes to the posts as a Grid in the bottom of the screen.
I ended up using the LazyVerticalGrid for the whole list and setting full span for the items that need to fill the entire screen:
LazyVerticalGrid(cells = GridCells.Fixed(3)) {
item(span = { GridItemSpan(3) }) { TopInfo() }
item(span = { GridItemSpan(3) }) { SomeOtherInfo() }
item(span = { GridItemSpan(3) }) { BottomInfo() }
items(gridItems) { GridItemView(it) }
}
I just ran into this problem myself. As #gaohomway said, your best bet is to use the experimental FlowRow() from Google's Accompanist library.
Here is a working code snippet as an example:
#Composable
fun ProfileScreen2() {
LazyColumn(
modifier = Modifier
.fillMaxSize()
) {
item {
Box(modifier = Modifier.fillMaxWidth().height(200.dp).background(color = Red))
}
item {
Box(modifier = Modifier.fillMaxWidth().height(200.dp).background(color = Gray))
}
item {
FlowRow() {
SampleContent()
}
}
}
}
#Composable
internal fun SampleContent() {
repeat(60) {
Box(
modifier = Modifier
.size(64.dp)
.background(Blue)
.border(width = 1.dp, color = DarkGray),
contentAlignment = Alignment.Center,
) {
Text(it.toString())
}
}
}
Displays this scrollable page (don't mind the nav bar at the bottom):
Reason
Nesting scrollable in the same direction layouts like LazyColumn and Column(Modifier.verticalScroll()) is not allowed.
Can't find LazyVerticalGrid forbidden scrolling temporarily
Alternatives
Alternative library from Android official Jetpack Compose Flow Layouts
FlowRow {
// row contents
}
FlowColumn {
// column contents
}
try using exact height for LazyVerticalGrid, it worked for me :
#ExperimentalFoundationApi
#Composable
fun Stats(stats: List<Stat>) {
LazyVerticalGrid(columns = GridCells.Fixed(2),
modifier = Modifier.height(50.dp)) {
itemsIndexed(stats) { index, item ->
StatBox(stat = item)
}
}
}
I ran into this same issue but in my case I just wanted to display 1-10 items with the same Composable component but different parameters, so I ended up creating a custom grid where you can pass:
List of composables
number of desired items per row
#Composable
fun VerticalGrid(composableList: List<#Composable () -> Unit>, itemsPerRow: Int) {
val components = composableList.toMutableList()
Column(Modifier.fillMaxWidth()) {
while (components.isNotEmpty()) {
val rowComponents: List<#Composable () -> Unit> = components.take(itemsPerRow)
val listOfSpacers: List<#Composable () -> Unit> = listOfSpacers(itemsPerRow - rowComponents.size)
RowWithItems(items = rowComponents.plus(listOfSpacers))
components.removeAll(rowComponents)
}
}
}
private fun listOfSpacers(number: Int): List<#Composable () -> Unit> {
val mutableList = emptyList<#Composable () -> Unit>().toMutableList()
repeat(number) {
mutableList.add { Spacer(Modifier) }
}
return mutableList.toList()
}
#Composable
private fun RowWithItems(items: List<#Composable () -> Unit>) {
Row(Modifier.fillMaxWidth()) {
items.forEach { item ->
Box(Modifier.weight(1f)) {
item()
}
}
}
}
Example on how to call:
VerticalGrid(
composableList = listOf(
{ ProfileDataField(value = profileInfo.country.name) },
{ ProfileDataField(value = profileInfo.dateOfBirth) },
{ ProfileDataField(value = profileInfo.gender) }
),
itemsPerRow = 2
)
Might not be the best for performance and it's definitely not lazy but currently there is no non-lazy Grid for this purpose.
Related
I'm new to Jetpack Compose and I'm not quite sure how to do what I need. In the screen below, I want to scroll the whole screen and not just the list at the bottom and when the scroll reaches the end of the list below, it still applies the paging library and goes to get more elements. I managed to get the Paging Library to work and the scroll in the list below too, but I can't make the rest of the page elements scroll as well - this is because only the list has scroll and not the rest of the page. Whenever I'm trying to do that, I get the following crash:
Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.
and I don't really know why.
I leave you the code below and two screenshots: the first is the current state, where I can only scroll through the list. The second is what I intend, which is to scroll the entire page.
#Edit: I was able to implement all screen scroll with fixed height on the children lazy column, but that is not what I want.
#Composable
#ExperimentalFoundationApi
private fun MainActivityLayout(navController: NavHostController) {
LazyColumn(
modifier = Modifier
.paint(
painter = painterResource(id = R.drawable.main_background),
contentScale = ContentScale.FillBounds
)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
item {
HeightSpacer(Dimen40)
Image(
painter = painterResource(id = R.drawable.ic_clearjobs_logo_2x),
contentDescription = null
)
HeightSpacer(Dimen47)
Navigation(navController = navController)
}
}
}
#Composable
#ExperimentalFoundationApi
fun JobOpeningsScreen(viewModel: JobOpeningsViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()
Column {
ClearJobsScreenTitle(
lightTitle = stringResource(id = R.string.job_openings_light_title),
boldTitle = stringResource(id = R.string.job_openings_bold_title)
)
HeightSpacer(Dimen60)
Row {
CategoryButton()
WidthSpacer(Dimen2)
OrderByButton()
}
HeightSpacer(Dimen30)
SearchTextField()
HeightSpacer(Dimen60)
when (uiState) {
is BaseViewState.Data -> JobOpeningsContent(
viewState = uiState.cast<BaseViewState.Data<JobOpeningsViewState>>().value
)
is BaseViewState.Loading -> {
LoadingView()
}
else -> {}
}
LaunchedEffect(key1 = viewModel, block = {
viewModel.onTriggerEvent(JobOpeningsEvent.LoadJobOffers)
})
}
}
#Composable
fun JobOpeningsContent(viewState: JobOpeningsViewState) {
val pagingItems = rememberFlowWithLifecycle(viewState.pagedData).collectAsLazyPagingItems()
SwipeRefresh(
state = rememberSwipeRefreshState(
isRefreshing = pagingItems.loadState.refresh == LoadState.Loading
),
onRefresh = { pagingItems.refresh() },
indicator = { state, trigger ->
SwipeRefreshIndicator(
state = state,
refreshTriggerDistance = trigger,
scale = true
)
},
content = {
LazyColumn(
modifier = Modifier.width(Dimen320),
verticalArrangement = Arrangement.spacedBy(Dimen30)
) {
items(pagingItems.itemCount) { index ->
pagingItems[index]?.let {
JobOpeningsRow(dto = it)
}
}
if (pagingItems.loadState.append == LoadState.Loading) {
item {
Box(
Modifier
.padding(24.dp)
) {
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
}
}
}
}
)
}
I found the solution to this problem, although it is not 100% and in terms of code it is not as good as I would like.
The error speaks for itself, we can't have infinite vertical scroll, Jetpack Compose doesn't allow it. I had the option of putting a fixed height on the Lazy Column of my list, but it wasn't what I wanted and it didn't work properly. The solution was to put everything inside a single LazyColumn and remove the Column from MainActivity, using a Box element and contentAlignment. I leave you below the final code that I used to solve the problem.
MainScreen function that before was MainActivityLayout function:
#Preview
#Composable
#ExperimentalFoundationApi
fun MainScreen() {
val navController = rememberNavController()
val topLevelDestinations = listOf(
NavigationItem.JobOpenings,
NavigationItem.Profile,
NavigationItem.About
)
val isTopLevelDestination =
navController
.currentBackStackEntryAsState()
.value
?.destination
?.route in topLevelDestinations.map { it.route }
val backStackEntryState = navController.currentBackStackEntryAsState()
Scaffold(
bottomBar = {
if (isTopLevelDestination) {
BottomNavBar(
navController = navController,
backStackEntryState = backStackEntryState,
bottomNavItems = topLevelDestinations
)
}
}
) {
Box(
modifier = Modifier
.paint(
painter = painterResource(id = R.drawable.main_background),
contentScale = ContentScale.FillBounds
)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Navigation(navController = navController)
}
}
}
New JobOpenings fun that is mixed with old JobOpeningsContent function:
#Composable
#ExperimentalFoundationApi
fun JobOpeningsScreen(viewModel: JobOpeningsViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()
when (uiState) {
is BaseViewState.Data -> {
val pagedData = uiState.cast<BaseViewState.Data<JobOpeningsViewState>>().value.pagedData
val pagingItems = rememberFlowWithLifecycle(pagedData).collectAsLazyPagingItems()
SwipeRefresh(
state = rememberSwipeRefreshState(
isRefreshing = pagingItems.loadState.refresh == LoadState.Loading
),
onRefresh = { pagingItems.refresh() },
indicator = { state, trigger ->
SwipeRefreshIndicator(
state = state,
refreshTriggerDistance = trigger,
scale = true
)
},
content = {
LazyColumn(
modifier = Modifier
.width(Dimen320),
verticalArrangement = Arrangement.spacedBy(Dimen30)
) {
item {
ScreenHeader(
lightTitle = stringResource(id = R.string.job_openings_light_title),
boldTitle = stringResource(id = R.string.job_openings_bold_title)
)
HeightSpacer(Dimen60)
Row {
CategoryButton()
WidthSpacer(Dimen2)
OrderByButton()
}
HeightSpacer(Dimen30)
SearchTextField()
HeightSpacer(Dimen60)
}
items(pagingItems.itemCount) { index ->
pagingItems[index]?.let {
JobOpeningsRow(dto = it)
}
}
if (pagingItems.loadState.append == LoadState.Loading) {
item {
Box(Modifier.padding(Dimen24)) {
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
}
}
}
}
)
}
is BaseViewState.Loading -> LoadingView()
else -> {}
}
LaunchedEffect(key1 = viewModel, block = {
viewModel.onTriggerEvent(JobOpeningsEvent.LoadJobOffers)
})
}
#ExperimentalFoundationApi
#Preview
#Composable
fun JobOpenings() {
JobOpeningsScreen()
}
Problems that I found with this solution:
LoadingView appears at the top of the screen instead at the top of the list.
If anyone has any suggestion to improve this, I am open to it. This works perfectly with Paging Library + Swipe Refresh (Accompanist) and full page scroll.
here's my situation: I have to show in my app a detail of a record I receive from API. Inside this view, I may or may not need to show some data coming from another viewmodel, based on a field.
Here my code:
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun ViewDetail(viewModel: MainViewModel, alias: String?, otherViewModel: OtherViewModel) {
viewModel.get(alias)
Scaffold {
val isLoading by viewModel.isLoading.collectAsState()
val details by viewModel.details.collectAsState()
when {
isLoading -> LoadingUi()
else -> Details(details, otherViewModel)
}
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
private fun Details(details: Foo?, otherViewModel: OtherViewModel) {
details?.let { sh ->
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState),
) {
Text(sh.title, fontSize = 24.sp, lineHeight = 30.sp)
Text(text = sh.description)
if (sh.other.isNotEmpty()) {
otherViewModel.load(sh.other)
val others by otherViewModel.list.collectAsState()
Others(others)
}
}
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
private fun Others(others: Flow<PagingData<Other>>) {
val items: LazyPagingItems<Other> = others.collectAsLazyPagingItems()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
contentPadding = PaddingValues(16.dp),
) {
items(items = items) { item ->
if (item != null) {
Text(text = item.title, fontSize = 24.sp)
Spacer(modifier = Modifier.height(4.dp))
Text(text = item.description)
}
}
if (items.itemCount == 0) {
item { EmptyContent() }
}
}
}
All the description here may be very long, both on the main Details body or in the Others (when present), so here's why the scroll behaviour requested.
Problem: I get this error:
Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()).
I hoped that .wrapContentHeight() inside LazyColumn would do the trick, but to no avail.
Is this the right way to do it?
Context: all packages are updated to the latest versions available on maven
The main idea here is to merge your Column with LazyColumn.
As your code is not runnable, I'm giving more a pseudo code, which should theoretically work.
Also calling otherViewModel.load(sh.other) directly from Composable builder is a mistake. According to thinking in compose, to get best performance your view should be side effects free. To solve this issue Compose have special side effect functions. Right now your code is gonna be called on each recomposition.
if (sh.other.isNotEmpty()) {
LaunchedEffect(Unit) {
otherViewModel.load(sh.other)
}
}
val others by otherViewModel.list.collectAsState()
LazyColumn(
modifier = Modifier
.fillMaxSize()
.wrapContentHeight(),
contentPadding = PaddingValues(16.dp),
) {
item {
Text(sh.title, fontSize = 24.sp, lineHeight = 30.sp)
Text(text = sh.description)
}
items(items = items) { item ->
if (item != null) {
Text(text = item.title, fontSize = 24.sp)
Spacer(modifier = Modifier.height(4.dp))
Text(text = item.description)
}
}
if (items.itemCount == 0) {
item { EmptyContent() }
}
}
You can use a system like the following
#Composable
fun Test() {
Box(Modifier.systemBarsPadding()) {
Details()
}
}
#Composable
fun Details() {
LazyColumn(Modifier.fillMaxSize()) {
item {
Box(Modifier.background(Color.Cyan).padding(16.dp)) {
Text(text = "Hello World!")
}
}
item {
Box(Modifier.background(Color.Yellow).padding(16.dp)) {
Text(text = "Another data")
}
}
item {
Others()
}
}
}
#Composable
fun Others() {
val values = MutableList(50) { it }
values.forEach {
Box(
Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = "Value = $it")
}
}
}
The result with scroll is:
The Code A is from the offical sample project here.
The Code B is from Android Studio source code.
I have searched the article about the function key by Google, but I can't find more details about it.
How can Android Studio launch the inline fun <T> key()? Why can't the author use Code C to launch directly?
Code A
key(detailPost.id) {
LazyColumn(
state = detailLazyListState,
contentPadding = contentPadding,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxSize()
.notifyInput {
onInteractWithDetail(detailPost.id)
}
) {
stickyHeader {
val context = LocalContext.current
PostTopBar(
isFavorite = hasPostsUiState.favorites.contains(detailPost.id),
onToggleFavorite = { onToggleFavorite(detailPost.id) },
onSharePost = { sharePost(detailPost, context) },
modifier = Modifier.fillMaxWidth().wrapContentWidth(Alignment.End)
)
}
postContentItems(detailPost)
}
}
Code B
#Composable
inline fun <T> key(
#Suppress("UNUSED_PARAMETER")
vararg keys: Any?,
block: #Composable () -> T
) = block()
Code C
LazyColumn(
state = detailLazyListState,
contentPadding = contentPadding,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxSize()
.notifyInput {
onInteractWithDetail(detailPost.id)
}
) {
stickyHeader {
val context = LocalContext.current
PostTopBar(
isFavorite = hasPostsUiState.favorites.contains(detailPost.id),
onToggleFavorite = { onToggleFavorite(detailPost.id) },
onSharePost = { sharePost(detailPost, context) },
modifier = Modifier.fillMaxWidth().wrapContentWidth(Alignment.End)
)
}
postContentItems(detailPost)
}
From key documentation:
key is a utility composable that is used to "group" or "key" a block of execution inside of a composition. This is sometimes needed for correctness inside of control-flow that may cause a given composable invocation to execute more than once during composition.
It also contains several examples, so check it out.
Here is a basic example of the usefulness of it. Suppose you have the following Composable. I added DisposableEffect to track its lifecycle.
#Composable
fun SomeComposable(text: String) {
DisposableEffect(text) {
println("appear $text")
onDispose {
println("onDispose $text")
}
}
Text(text)
}
And here's usage:
val items = remember { List(10) { it } }
var offset by remember {
mutableStateOf(0)
}
Button(onClick = {
println("click")
offset += 1
}) {
}
Column {
items.subList(offset, offset + 3).forEach { item ->
key(item) {
SomeComposable(item.toString())
}
}
}
I only display two list items, and move the window each time the button is clicked.
Without key, each click will remove all previous views and create new ones.
But with key(item), only the disappeared item disappears, and the items that are still on the screen are reused without recomposition.
Here are the logs:
appear 0
appear 1
appear 2
click
onDispose 0
appear 3
click
onDispose 1
appear 4
click
onDispose 2
appear 5
In xml you can use GridLayoutManager.SpanSizeLookup in GridLayoutManager to set the span size on single items (How many columns the item will use in the row, like for example, in a grid of 3 columns I can set the first item to be span size 3 so it will use all the width of the grid), but in Compose I can't find a way to do it, the vertical grid only have a way set the global span count and add items, but not set the span size of an individual item, is there a way to do it?
Jetpack Compose version 1.1.0-beta03 introduced horizontal spans to LazyVerticalGrid.
Example code:
val list by remember { mutableStateOf(listOf("A", "E", "I", "O", "U")) }
LazyVerticalGrid(
cells = GridCells.Fixed(2)
) {
// Spanned Item:
item(
span = {
// Replace "maxCurrentLineSpan" with the number of spans this item should take.
// Use "maxCurrentLineSpan" if you want to take full width.
GridItemSpan(maxCurrentLineSpan)
}
) {
Text("Vowels")
}
// Other items:
items(list) { item ->
Text(item)
}
}
There is no support for this out of the box at present. The way I have solved this for now is to use a LazyColumn then the items are Rows and in each Row you can decide how wide an item is, using weight.
I have implemented and in my case I have headers (full width), and cells of items of equal width (based on how wide the screen is, there could be 1, 2 or 3 cells per row). It's a workaround, but until there is native support from VerticalGrid this is an option.
My solution is here - look for the LazyListScope extensions.
Edit: this is no longer necessary as LazyVerticalGrid supports spans now, here's an example
LazyVerticalGrid(
columns = GridCells.Adaptive(
minSize = WeatherCardWidth,
),
modifier = modifier,
contentPadding = PaddingValues(all = MarginDouble),
horizontalArrangement = Arrangement.spacedBy(MarginDouble),
verticalArrangement = Arrangement.spacedBy(MarginDouble),
) {
state.forecastItems.forEach { dayForecast ->
item(
key = dayForecast.header.id,
span = { GridItemSpan(maxLineSpan) }
) {
ForecastHeader(
state = dayForecast.header,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = MarginDouble),
)
}
items(
items = dayForecast.forecast,
key = { hourForecast -> hourForecast.id }
) { hourForecast ->
ForecastWeatherCard(
state = hourForecast,
modifier = Modifier.fillMaxWidth(),
)
}
}
}
Adapting the code from the answer, I created a more "general" purpose method, It can be used with Adaptive and Fixed, I'm very new with Compose so I accept suggestions
#Composable
fun HeaderGrid(cells: GridCells, content: HeaderGridScope.() -> Unit) {
var columns = 1
var minColumnWidth = 0.dp
when (cells) {
is GridCells.Fixed -> {
columns = cells.count
minColumnWidth = cells.minSize
}
is GridCells.Adaptive -> {
val width = LocalContext.current.resources.displayMetrics.widthPixels
val columnWidthPx = with(LocalDensity.current) { cells.minSize.toPx() }
minColumnWidth = cells.minSize
columns = ((width / columnWidthPx).toInt()).coerceAtLeast(1)
}
}
LazyColumn(modifier = Modifier.fillMaxWidth()){
content(HeaderGridScope(columns, minColumnWidth, this))
}
}
fun <T>HeaderGridScope.gridItems(items: List<T>, content: #Composable (T) -> Unit) {
items.chunked(numColumn).forEach {
listScope.item {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
it.forEach {
content(it)
}
if (it.size < numColumn) {
repeat(numColumn - it.size) {
Spacer(modifier = Modifier.width(columnWidth))
}
}
}
}
}
}
fun HeaderGridScope.header(content: #Composable BoxScope.() -> Unit) {
listScope.item {
Box(
modifier = Modifier
.fillMaxWidth(),
content = content
)
}
}
data class HeaderGridScope(val numColumn: Int, val columnWidth: Dp, val listScope: LazyListScope)
sealed class GridCells {
class Fixed(val count: Int, val minSize: Dp) : GridCells()
class Adaptive(val minSize: Dp) : GridCells()
}
I'm trying to iterate over a List of objects, for each of the object I want to display a composable card. The problem is that you cant call Composable functions from inside the list.forEach{} brackets.
The Code:
#Composable
fun Greeting(listy : List<SomethingForLater>) {
LazyColumn {
listy.forEach {
//error here
testCard(somethingForLater = it)
}
}
}
#Composable
fun testCard(somethingForLater: SomethingForLater){
val theme = MaterialTheme
Card(shape = theme.shapes.small,backgroundColor = theme.colors.secondary){
Column {
Row {
Text(
text = somethingForLater.task,
modifier = Modifier.padding(start = 5.dp,
top = 3.dp,bottom = 3.dp
),
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
)
}
}
}
}
There is items parameter in LazyColumn
LazyColumn {
items(listy) { message ->
testCard(message)
}
}
Or you can simply change LazyColumn to Column
LazyColumn does not provide any composable content. So you have to wrap your composable functions inside items parameter.
#Composable
fun Greetings(listy : List<SomethingForLater>) {
LazyColumn {
items(listy.size) {
listy.forEach { somethingForLater ->
TestCard(somethingForLater = somethingForLater)
}
}
}
}