3

I found out that my app uses an unusually large amount of memory. There's a LazyColumn where data is updated regularly. I found out that after these updates the amount of list item instances in memory increases with time and grows to become significantly larger than the maximum amount of items in the list at once.
I made a small sample illustrating the problem here.

These are data items:

@Immutable
data class TestDataBlock(
    val id: String,
    val data: Int
)

The LazyColumn looks like this.

@Composable
fun List(dataItems: List<TestDataBlock>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        itemsIndexed(items = dataItems,
            key = { index, item ->
                item.id
            }
        ) { rowIndex, rowItem ->
            drawElement(rowItem)
        }
    }
}

@Composable
fun drawElement(rowItem: TestDataBlock) {
    Text(text = "${rowItem.data}")
}

In this sample, I set two portions of data in LazyColumn and after that, I clean it up.

@Composable
fun runTest() {
    var itemsState: MutableState<List<TestDataBlock>> = remember {
        mutableStateOf(listOf())
    }

    LaunchedEffect(Unit) {
        delay(1000)
        itemsState.value = MutableList<TestDataBlock>(30) { rowIndex ->            
            TestDataBlock(id = rowIndex.toString(), data = 1)
        }
        delay(1000)
        itemsState.value = MutableList<TestDataBlock>(30) { rowIndex ->            
            TestDataBlock(id = rowIndex.toString(), data = 2)
        }
        delay(1000)
        itemsState.value = listOf()
    }

    List(dataItems = itemsState.value)
}

I would expect no examples of items with references in memory (visible in profiler), but unfortunately, the last set of data is still in memory, referenced somewhere from Compose.
In the real app where items contain LazyRows and images such multiple updates lead to clogging up memory and potentially, to OOM.
The item classes are immutable (@Immutable), and all items have unique ids. I would be grateful for any ideas about the reasons of this behaviour and if it can be changed.

Qalterra
  • 73
  • 5
  • Welcome on StackOverflow. Please add some major Compose code to your question instead of adding a repository link. – Phil Dukhov Nov 17 '21 at 21:27

2 Answers2

1

You're retrieving your values from a MutableList and not from a Flow. A mutable list retians all of the items whereas a Flow will retrieve only the items that are visible plus several more either before the first visible item or after the last visible item. You should use the paging library with a Flow to populate your LazyList. Here's an article you can read on using pagination with Compose and LazyColum:

List view with Pagination using Jetpack Compos

Johann
  • 27,536
  • 39
  • 165
  • 279
  • 1
    Thanks for answering! The thing is, in this example, I clean the mutable list, the LazyColumn is also cleaned and the items are still in memory: itemsState.value = listOf() In real app, I use Flow to fill the LazyColumn with data. – Qalterra Nov 17 '21 at 19:50
0

I've filled out a bug for Google which they have reproduced: https://issuetracker.google.com/issues/207946467

Qalterra
  • 73
  • 5