简体   繁体   中英

Proper use of mutableStateflow with Launcheffect in jetpack compose

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
                    }
                },
                onError = {
                    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

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
                    }
                },
                onError = {
                    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
                }
            },
            onError = {
                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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM