Home > Blockchain >  Only first emitted value by StateFlow is being reflected in Compose UI
Only first emitted value by StateFlow is being reflected in Compose UI

Time:03-31

I am working with Compose Desktop. I have created a ViewModel class that is emitting some states as follows

    internal class ImportViewModel(private val dbHelper: FirestoreHelper) {
    private val scope = CoroutineScope(Dispatchers.Default)

    private val uiState = MutableStateFlow<ImportUiState>(InitialState)
    fun uiState() = uiState as StateFlow<ImportUiState>

    fun beginImport(filePath: String) {
        scope.launch {
            try {
                update("Before Delay")
                delay(100)
                update("Delay 1")
                delay(100)
                update("Delay 2")
                delay(100)
                update("Delay 3")
                delay(100)
                update("Delay 4")
                delay(100)
                update("Delay 5")
            } catch (exception: Exception) {
                println(exception.stackTrace)
                update(exception.localizedMessage)
            }

Where update method is defined as follows

private fun update(text: String) {
        println(text)
        scope.launch {
            uiState.emit(ProgressState(text))
        }
    }

I am collecting this StateFlow in my composable as follows

@Composable
fun ImportTab() {
    var progressText by remember { mutableStateOf("") }
    val viewModel = ImportViewModel(FirestoreHelper)
    val uiState = viewModel.uiState().collectAsState().value

    Column(modifier = Modifier.padding(24.dp)) {
        Button(onClick = {
            viewModel.beginImport("")
        }) {
            Text("Begin Import")
        }

        if (uiState is ProgressState) {
            progressText = uiState.status
        }

        if (progressText.isNotEmpty()) {
            Text(text = progressText)
        }
    }
}

The problem is in UI I cannot get progressText to get reflected after the first emission. I can see proper text in the console. I have tried playing around with Dispatchers and working with uiState.status directly instead of progressText but no luck.

CodePudding user response:

Problem seems to be this line

     val viewModel = ImportViewModel(FirestoreHelper)

ViewModel probably being is recreated on every recomposition. Injecting it in the Composable method fixes the problem for me.

@Composable
internal fun ImportTab(viewModel: ImportViewModel) {
..
}

Still not sure the best way to inject such dependencies in a Composable method though.

CodePudding user response:

If you're using Android, you can use view models as showed in documentation.

In Compose for Desktop these are not available at the moment.

To save a view model between recomposition, you can use remember. As far as I know Desktop doesn't have configuration changes like Android rotations, so remember value should live until the view is in the view tree.

val viewModel = remember { ImportViewModel(FirestoreHelper) }
  • Related