Home > OS >  Proper use of mutableStateflow with Launcheffect in jetpack compose
Proper use of mutableStateflow with Launcheffect in jetpack compose

Time:07-21

Hey I am working in MutableStateFlow in jetpack compose. I am trying to load spinner in next screen but it's not working. I am started learning jetpack compose. I didn't find the proper way in jetpack compose. I know MutableStateFlow in legacy way. I'll try explain what I am trying to do and what I want to achieve. I have a Button in the composable function, when I press the Button I am going into the viewModel and calling the api inside viewModelScope.launch. On that basis I have create a class called ResultFetchState which is sealed class, inside that I used UI state. When my api get any thing it will route to success, error and loading. The main problem is when I am using collectAsState() the data is going onSuccess but onLoading is not working. Hope you understand this.

MainActivityViewModel.kt

class MainActivityViewModel(private val resultRepository: ResultRepository) : ViewModel() {

    val stateResultFetchState = MutableStateFlow<ResultFetchState>(ResultFetchState.OnEmpty)

    fun getSportResult() {
        viewModelScope.launch {
            val result = resultRepository.getSportResult()
            delay(5000)
            result.handleResult(
                onSuccess = { response ->
                    if (response != null) {
                        stateResultFetchState.value = ResultFetchState.IsLoading(false)
                        stateResultFetchState.value = ResultFetchState.OnSuccess(response)
                    } else {
                        stateResultFetchState.value = ResultFetchState.OnEmpty
                    }
                },
                one rror = {
                    stateResultFetchState.value =
                        ResultFetchState.OnError(it.errorResponse?.errorMessage)
                }
            )
        }
    }
}

I am not adding my whole activity code. I am adding my composable function. If you need to see my whole activity click on class name below.

MainActivity.kt

@OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun SetupView(viewModel: MainActivityViewModel = koinViewModel()) {
        Scaffold(topBar = {
            TopAppBar(
                title = { Text(text = stringResource(id = R.string.app_name)) },
                backgroundColor = getBackgroundColor(),
                elevation = 0.dp
            )
        }, content = { padding ->
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .background(getBackgroundColor())
                    .padding(padding),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Button(onClick = {
                    viewModel.getSportResult()
                }) {
                    Text(text = stringResource(id = R.string.get_result))
                }
            }
        })
        when (val state = viewModel.stateResultFetchState.collectAsState().value) {
            is ResultFetchState.OnSuccess -> {
                logE("data >> ${state.sportResultResponse}")
            }
            is ResultFetchState.IsLoading -> {
                if (state.isLoading) {
                    logE("data loading >")
                    ResultScreen()
                }
            }
            is ResultFetchState.OnError -> {
                logE("data >> ${state.response}")
            }
            is ResultFetchState.OnEmpty -> {}
        }
    }

I though there is problem in my lifecycle. So I search in stack overflow of LaunchedEffect but still not working. Another question is there any use of LaunchedEffect in my code I am little bit confused on this side effect. My github project link. Thanks

CodePudding user response:

You are setting value of stateResultFetchState consecutively

  fun getSportResult() {
        viewModelScope.launch {
            val result = resultRepository.getSportResult()
            delay(5000)
            result.handleResult(
                onSuccess = { response ->
                    if (response != null) {
                        stateResultFetchState.value = ResultFetchState.IsLoading(false)
                        stateResultFetchState.value = ResultFetchState.OnSuccess(response)
                    } else {
                        stateResultFetchState.value = ResultFetchState.OnEmpty
                    }
                },
                one rror = {
                    stateResultFetchState.value =
                        ResultFetchState.OnError(it.errorResponse?.errorMessage)
                }
            )
        }
    }

You can set to loading before getting sport results and in your ui you check if loading is true you need to set it with true

fun getSportResult() {
    viewModelScope.launch {
        stateResultFetchState.value = ResultFetchState.IsLoading(true)
        val result = resultRepository.getSportResult()
        delay(5000)
        result.handleResult(
            onSuccess = { response ->
                if (response != null) {
                    stateResultFetchState.value = ResultFetchState.OnSuccess(response)
                } else {
                    stateResultFetchState.value = ResultFetchState.OnEmpty
                }
            },
            one rror = {
                stateResultFetchState.value =
                    ResultFetchState.OnError(it.errorResponse?.errorMessage)
            }
        )
    }
}

Also there is an issue with your UI either. Even if you correctly get loading state you might be able to see them because Scaffold places children in content Composable on top of each other with

bodyContentPlaceables.forEach {
            it.place(0, 0)
        }

You might need to use Composable that covers your content area as root Comopsable.

  • Related