Android Questions: Spot Bugs

Published in Android开发问答, 2025

When this code is run, the Column doesn’t render the background color that was applied to it. Why is that?

@Composable
fun MainScreen(
    modifier: Modifier = Modifier,
    nightModeEnabled: Boolean = false
) {
    val screenModifier = modifier
        .background(color = AppColors.background)
        .fillMaxSize()

    AppTheme(darkMode = nightModeEnabled) {
        Column(modifier = screenModifier) {
            // Your content here
        }
    }
}

Answer: 主题覆盖了背景:AppTheme 可能已经为其内容设置了背景色,这会覆盖你在 Column 上设置的背景色

Solution:

@Composable
fun MainScreen(
    modifier: Modifier = Modifier,
    nightModeEnabled: Boolean = false
) {
    AppTheme(darkMode = nightModeEnabled) {
        Column(
            modifier = modifier
                // Here, the background color is accessed
                // only in the scope of the AppTheme
                .background(color = AppColors.background)
                .fillMaxSize()
        ) {
            // Your content here
        }
    }
}

When an item in the list is clicked, our users keep tripping a ConcurrentModificationException. Can you see why the bug that makes the app crash?

@Composable
fun SmoothScrollingList(
    items: List<String>,
    onItemClick: (String) -> Unit
) {
    val listState = rememberLazyListState()
    val scope = rememberCoroutineScope()

    LazyColumn(state = listState) {
        items(items) { item ->
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(8.dp)
                    .clickable {
                        onItemClick(item)            
                        scope.launch {
                            listState.animateScrollToItem(0)
                        }
                    }
            ) {
                Text(item, Modifier.padding(16.dp))
            }
        }
    }
}

How do we fix it? Re-order the operations— Scroll first (while the list is still intact), then mutate.

@Composable fun SmoothScrollingList( items: List<String>, onItemClick: (String) -> Unit ) { val listState = rememberLazyListState() val scope = rememberCoroutineScope()
LazyColumn(state = listState) {
    items(
        items = items,
        key = { it }          // stable keys help recycling
    ) { item ->
        Card(
            modifier = Modifier
                .fillMaxWidth()
                .padding(8.dp)
                .clickable {
                    scope.launch {
                        // ⬅️ scroll first mutate afterwards
                        listState.animateScrollToItem(0)  
                        onItemClick(item)                
                    }
                }
        ) {
            Text(item, Modifier.padding(16.dp))
        }
    }
}

When this code runs, scrolling the list causes items to lose their expanded state unexpectedly. Why does this happen?

@Composable
fun ItemsList(items: List<String>) {
LazyColumn {
itemsIndexed(items) { index, item ->
var isExpanded by remember { mutableStateOf(false) }

          Card(
              modifier = Modifier
                  .fillMaxWidth()
                  .clickable { isExpanded = !isExpanded }
          ) {
              Column {
                  Text(text = item)
                  if (isExpanded) {
                      Text(
                          text = "Extended for $item",
                      )
                  }
              }
          }
      }
}
}

Answer: The issue is that remember without a key doesn’t survive item recomposition during scrolling. LazyColumn recycles composables, so the isExpanded state gets reset when items scroll in and out of view.

Fix it by providing a stable key:

var isExpanded by remember(item) { mutableStateOf(false) }
// or better yet, manage state outside the composable

发表评论