Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack

How to add a CircularProgressIndicator on top of existing LazyColumn in Jetpack Compose?


I have the following screen:

fun ItemsScreen(
    viewModel: ItemsViewModel = hiltViewModel()
) {
    val showProgressBarState = remember { mutableStateOf(false) }
    if (showProgressBarState.value) { ShowProgressBar() }

    when(val resource = viewModel.state.value) {
        is Loading -> ShowProgressBar() //Works perfectly
        is Success -> LazyColumn {
            items(
                items = resource.data
            ) { item ->
                ItemCard(
                    item = item
                )
            }
            when(val r = viewModel.s.value) {
                is Loading -> showProgressBarState.value = true
                is Success -> showProgressBarState.value = false
                is Failure -> Log.d(TAG, "Failure")
            }
        }
        is Failure -> Text(
            text = r.message,
            modifier = Modifier.padding(16.dp)
        )
    }
}

@Composable
fun ShowProgressBar() {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        CircularProgressIndicator()
    }
}

The second "when" is for a delete state. I want when a item is deleted to start the progress bar. It starts but behind the LazyColumn. How to add it in front?

This is what I have in the activity class:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        Scaffold(
            //
        ) {
            ItemsScreen()
        }
    }
}

Solution

  • You're updating your state value inside the view builder. It'll work in this case, but generally it's a bad practice which may lead to redundant recompositions which may slow your app. I'm talking about this part:

    when(val r = viewModel.s.value) {
        is Loading -> showProgressBarState.value = true
        is Success -> showProgressBarState.value = false
        is Failure -> Log.d(TAG, "Failure")
    }
    

    You should change state using side effects. Here you could've use LaunchedEffect, so content would be called only when specified key is changed since the last recomposition.:

    LaunchedEffect(viewModel.s.value) {
        when (val r = viewModel.s.value) {
            is Loading -> showProgressBarState.value = true
            is Success -> showProgressBarState.value = false
            is Failure -> Log.d(TAG, "Failure")
        }
    }
    

    But actually that was an off topic for the future, in this case you don't need showProgressBarState at all.

    When you use Box, items are displayed on the screen on on top of each other. As you need to display the progress bar on top of LazyColumn, you need to wrap it with a Box and place ShowProgressBar after LazyColumn.

    Also you can specify contentAlignment = Alignment.Center instead of wrapping CircularProgressIndicator with a Column:

    is Resource.Success -> {
        Box(contentAlignment = Alignment.Center) {
            LazyColumn {
                items(
                    items = resource.data
                ) { item ->
                    ItemCard(
                        item = item
                    )
                }
            }
            when (val r = viewModel.s.value) {
                is Resource.Loading -> CircularProgressIndicator()
                is Resource.Success -> Unit
                is Resource.Failure -> Log.d(TAG, "Failure")
            }
        }
    }