简体   繁体   中英

Avoid Recomposition in nested column in jetpack compose

I am working on nested column in jetpack compose. I have one list which is huge amount of data coming from server. I was checked in Layout Inspector and I see that whenever my item is added in list it recompose and increase counts. So my doubt is if I add 100 item in list one by one, so my Nested Column will be 100 times recompose ? If not can someone help me on this please?

ListViewComposableActivity.kt

class ListViewComposableActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppBarScaffold(
                displayHomeAsUpEnabled = true,
                titleId = R.string.activity
            ) {
                ListViewItemStateful()
            }
        }
    }
}

ListViewItemStateful

@Composable
fun ListViewItemStateful(
    viewModel: ListViewModel = koinViewModel(),
) {
    ItemViewListStateless(
        uiState = viewModel.uiState,
        isEnable = viewModel.isEnable,
        scanDeviceList = viewModel.scanResultList,
    )
}

ItemViewListStateless

@Composable
fun ItemViewListStateless(
    uiState: State,
    isEnable: Boolean,
    scanDeviceList: SnapshotStateList<ScanResults>,
) {
    when (uiState) {
        INITIAL,
        FIRST -> {
            ListContent(isEnable, scanDeviceList)
        }
    }
}

ListContent

@Composable
fun ListContent(isEnable: Boolean, scanDeviceList: SnapshotStateList<ScanResults>) {
    AnimatedVisibility(true) {
        Column(
            modifier = Modifier
                .padding(16.dp)
                .fillMaxSize()
                .verticalScroll(rememberScrollState()),
        ) {
            if (isEnable) {
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    DeviceList(
                        scanDeviceList,
                        modifier = Modifier.align(Alignment.Start),
                    )
                }
            }
        }
    }
}

DeviceList

@Composable
fun ColumnScope.DeviceList(
    scanDeviceList: SnapshotStateList<ScanResults>,
    modifier: Modifier = Modifier,
) {
    Spacer(modifier = Modifier.height(32.dp))
    AnimatedVisibility(
        scanDeviceList.isNotEmpty(),
        modifier = modifier
    ) {
        Column {
            Text(text = "Device List")
            scanDeviceList.forEachIndexed { index, scanResults ->
                Text(text = scanResults.device.name)
            }
        }
    }
}

ListViewModel.kt

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope
import com.abc.app.common.BaseViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class ListViewModel : BaseViewModel() {

    val scanResultList by lazy { mutableStateListOf<ScanResults>() }
    var isEnable by mutableStateOf(false)
        private set
    var uiState by mutableStateOf<State>(State.INITIAL)
        private set

    init {
        viewModelScope.launch {
            (0..10).forEach {
                delay(2000)
                scanResultList.add(ScanResults(Device("item $it")))
            }
        }
        isEnable = true
        uiState = State.FIRST
    }
}


data class ScanResults(val device: Device)
data class Device(val name: String)
enum class State {
    INITIAL,
    FIRST
}

I am adding few items in list to show in layout inspector

在此处输入图像描述

In above image you can see the DeviceList is recompose 10 times.

I checked in Jetpack Compose: Debugging recomposition around 6:40 min he tried to solve recompose issue and there skipped recomposition count is clear. So why it's showing count in my component tree in recomposition and skipped section? Many thanks

In your question when you add a new item to SnapshotStateList whole Column is composed because Column doesn't create a composition scope due to inline keyword. If you create a scope that scope is recomposed when the value it reads changes. You can refer this question and answer as well.

Jetpack Compose Smart Recomposition

Add an item where Text reads device

@Composable
private fun Item(device: Device) {
    Text(
        modifier = Modifier.border(2.dp, getRandomColor()),
        text = device.name
    )
}

Random color is something i use for displaying recomposition visually

fun getRandomColor() =  Color(
    red = Random.nextInt(256),
    green = Random.nextInt(256),
    blue = Random.nextInt(256),
    alpha = 255
)

Your current setup

在此处输入图像描述

With Item Composable that creates scope.

@Composable
fun ColumnScope.DeviceList(
    scanDeviceList: SnapshotStateList<ScanResults>,
    modifier: Modifier = Modifier,
) {
    Spacer(modifier = Modifier.height(32.dp))
    AnimatedVisibility(
        scanDeviceList.isNotEmpty(),
        modifier = modifier
    ) {
        Column {
            Text(text = "Device List", color = getRandomColor())
            scanDeviceList.forEachIndexed { index, scanResults ->
//                Text(
//                    modifier = Modifier.border(2.dp, getRandomColor()),
//                    text = scanResults.device.name
//                )

                Item(scanResults.device)
            }
        }
    }
}

在此处输入图像描述

And when you have many items, especially they don't fit viewport you can use LazyColumn instead of Column with verticalScroll to limit recomposition amount to number of items that are visible on viewport or visible area of LazyColumn

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM