Search code examples
androidkotlinandroid-jetpack-composekotlin-stateflowlinearprogressindicator

How can I use StateFlow with LinearProgressIndicator to show progress


I'm new to Compose framework and StateFlows. I want LinearProgressIndicator to show progress that will be emitted from downloadProgress StateFlow. How can I achieve that?

@Composable
fun DownloadProgressDialog(
    downloadProgress: StateFlow<Float>,
    onDismissRequest: () -> Unit = {},
) {
    // how to pass downloadProgress to LinearProgressIndicator?
    var progress by remember { mutableStateOf(0f) }
    val animatedProgress = animateFloatAsState(
        targetValue = progress,
        animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
    ).value

    Dialog(onDismissRequest = { onDismissRequest() }) {
        Surface {
            Column {
                LinearProgressIndicator(progress = animatedProgress)
            }
        }
    }
}

Solution

  • You need a State or MutableState to schedule recomposition.

    https://stackoverflow.com/a/70217911/5457853

    And you can have a stateless Composable by changing DownloadProgressDialog input with Float as

    @Composable
    fun DownloadProgressDialog(
        downloadProgress: Float,
        onDismissRequest: () -> Unit = {},
    ) {
        val animatedProgress by animateFloatAsState(
            targetValue = downloadProgress,
            animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
            label = "",
        )
    
        Dialog(onDismissRequest = { onDismissRequest() }) {
            Surface {
                Column {
                    LinearProgressIndicator(progress = animatedProgress)
                }
            }
        }
    }
    

    StateFlow has an extension function to represent it as State

    @Composable
    fun <T> StateFlow<T>.collectAsState(
        context: CoroutineContext = EmptyCoroutineContext
    ): State<T> = collectAsState(value, context)
    

    I made a sample with ViewModel but you can ignore it if not needed and use how StateFlow is converted to State

    class MyViewModel : ViewModel() {
        private var _progressFlow = MutableStateFlow(0f)
        val progressFlow = _progressFlow.asStateFlow()
    
        init {
            viewModelScope.launch {
                for (i in 0..100) {
                    delay(100)
                    _progressFlow.value = i/100f
                    println("ViewModel progress: ${progressFlow.value}")
                }
            }
        }
    }
    
    @Preview
    @Composable
    private fun Test() {
        val viewModel = remember {
            MyViewModel()
        }
        MyFun(viewModel = viewModel)
    }
    
    @Composable
    private fun MyFun(viewModel: MyViewModel) {
        val progress by viewModel.progressFlow.collectAsState()
        DownloadProgressDialog(downloadProgress = progress)
    }
    
    @Composable
    fun DownloadProgressDialog(
        downloadProgress: Float,
        onDismissRequest: () -> Unit = {},
    ) {
        val animatedProgress by animateFloatAsState(
            targetValue = downloadProgress,
            animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
            label = "",
        )
    
        Dialog(onDismissRequest = { onDismissRequest() }) {
            Surface {
                Column {
                    LinearProgressIndicator(progress = animatedProgress)
                }
            }
        }
    }