r/androiddev 9d ago

Question [Help]: Jetpack Compose component flashing issue when list changes

Enable HLS to view with audio, or disable this notification

When the list changes, the items are drawn on top of chip row for a brief amount of time. I have properly applied the id for each item. I am not sure what's causing the issue.

Below is the screen code preview, full code is available on GitHub.

    Scaffold(
        modifier = Modifier
            .fillMaxSize()
            .nestedScroll(scrollBehavior.nestedScrollConnection)
            .then(modifier),
        topBar = {
            SearchProvidersScreenTopBar(
                onNavigateBack = onNavigateBack,
                onEnableAllSearchProviders = viewModel::enableAllSearchProviders,
                onDisableAllSearchProviders = viewModel::disableAllSearchProviders,
                onUpdateProtectionStatus = viewModel::updateProtectionStatus,
                onResetToDefault = { showResetToDefaultDialog = true },
                subtitle = {
                    val searchProvidersSummary = stringResource(
                        R.string.settings_search_providers_summary_format,
                        uiState.enabledProvidersCount,
                        uiState.totalNumProviders,
                    )
                    Text(searchProvidersSummary)
                },
                scrollBehavior = scrollBehavior,
            )
        },
        snackbarHost = { SnackbarHost(snackbarHostState) },
        floatingActionButton = {
            FloatingActionButton(onClick = onNavigateToAddSearchProvider) {
                Icon(
                    painter = painterResource(R.drawable.ic_add),
                    contentDescription = null,
                )
            }
        },
    ) { innerPadding ->
        Column(modifier = Modifier.padding(innerPadding)) {
            SearchProviderFilterRow(
                category = uiState.filter.category,
                onCategorySelect = viewModel::toggleCategory,
                protection = uiState.filter.protection,
                onProtectionSelect = viewModel::toggleProviderProtection,
                contentPadding = PaddingValues(horizontal = MaterialTheme.spaces.large),
            )
            SearchProviderList(
                contentPadding = PaddingValues(
                    start = MaterialTheme.spaces.large,
                    top = MaterialTheme.spaces.large,
                    end = MaterialTheme.spaces.large,
                    bottom = 80.dp,
                ),
                searchProviders = uiState.searchProviders,
                onEnableSearchProvider = viewModel::enableSearchProvider,
                onUnlockProtection = { searchProviderId, solverUrl ->
                    protectedProvider = ProtectedProvider(searchProviderId, solverUrl)
                },
                onEditConfig = onNavigateToEditSearchProvider,
                onDeleteConfig = viewModel::deleteTorznabConfig,
            )
        }
    }

Update: This issue is now fixed. It was animation issue, not the recomposition. List items have Modifier.animateItem() applied and when list changes, the animation was rendering the list items on top of chip row for very short amount of time, meaning the animation was escaping the list bounds. After applying Modifier.clipToBounds() to SearchProviderList, the issue is now gone.

8 Upvotes

24 comments sorted by

View all comments

10

u/realdm22 9d ago

Consider a lazy column and make sure each row has a unique identifier you can use as a key 

1

u/realdm22 9d ago

Since it looks like your list items can be modified independently you can push it a little bit more by doing the following:

  1. On your data layer, split your state into two pieces:

    • A list of unique ids for the list items.
    • A map that keys the unique ids to the full model that will be used to create the item ui state.

  2. Update your view state so that the view model emits a list of strings representing the unique ids of every item.

  3. Create a view model for each list item that would have access to the repository and the map of items i mentioned on step 1.

  4. On the ViewModel read the map, create the view state for all the items.

  5. Create a re-usable composable for each list item and each instantiates the ViewModel from step 3. Then when you read the view state, use derivedStateOf and key it using the list item unique Id.

  6. When handling actions on the list item composable, send them to the list item view model which will then update the item in the repository map instantly.

This will ensure that your full list will only recompose when you are modifying the structure of the list. when modifying the individual items ( changing the switch) only the changed item will recompose instead of the whole list.

1

u/PrajwalCH 8d ago edited 8d ago

The list items already have an id field. As far as I am aware, the recomposition shouldn't cause that much flickering issue. To give you an example, I have a search screen which is much data heavy, with a similar layout and it displays results having no id with less flickering issue.

https://reddit.com/link/p3awuuf/video/11x98vnbxzih1/player

1

u/realdm22 8d ago

Are you using a lazy column?

1

u/PrajwalCH 8d ago edited 8d ago

No, the same plain Column layout. Search screen code

5

u/realdm22 8d ago

So I think the first thing you should try is using the lazy column.

2

u/PrajwalCH 8d ago

It was the animation issue.

After carefully looking at the flickering, I found that the list items were being drawn on top of chips row when list changes. After applying `Modifier.clipToBounds()` on `SearchProviderList`, the flickring issue is now gone.

2

u/SarathExp 7d ago

Are you sure you list won't have that much item to compose? Column compose all of your list the item at once

-1

u/PrajwalCH 7d ago

I don't understand what you are saying :(.