How to pass result from viewModelScope to activity

57 Views Asked by At

I want to pass the result from viewModelScope into my activity. I created viewModel which has 2 methods.

class CurrencyConverterViewModel : ViewModel() {

    lateinit var response: CurrencyCodesModel
    var listOfNames : List<String> = emptyList()

    init {
        viewModelScope.launch {
            getListOfCurrencyNamesApi()
        }
    }

    suspend fun getCurrencyCodes() {
        response = CurrencyConverterInstance
            .currencyApiService
            .getCurrencyCodes()
    }
    private suspend fun getListOfCurrencyNamesApi() {

        getCurrencyCodes()

        listOfNames = response.supported_codes.map { it[1] }
    }
}

One of them calls my api service and stores result in a response variable. The second method called getListOfCurrencyNamesApi is supposed to take supported_codes from response and split them into list of strings.

Then I create viewModel object in my Composable function. At this point the init function in viewModel is called and I see in Logcat that the list has some records but when I want to put it into currencyListOfNames.value it is empty

@Composable
fun CurrencyConverter() {

    val myViewModel: CurrencyConverterViewModel = viewModel()
    val currencyListOfNames = remember { mutableStateOf<List<String>>(emptyList()) }

    currencyListOfNames.value = myViewModel.listOfNames
}
2

There are 2 best solutions below

2
witodev On BEST ANSWER

here is a solution for you to solve your problem, the reason why you did not see the update of your data is because they are in different threads and therefore you had no way to know your composable that it was being updated and that it was recomposed again.

class CurrencyConverterViewModel : ViewModel() {

    lateinit var response: CurrencyCodesModel
    // Here you are creating the variable with a stateFlow but you can also do it with a SharedFlow the difference you can see here https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
    private var _listOfNames = MutableStateFlow<List<String>>(listOf())
    val  listOfNames = _listOfNames.asStateFlow()

    init {
        viewModelScope.launch {
            getListOfCurrencyNamesApi()
        }
    }

    suspend fun getCurrencyCodes() {
        response = CurrencyConverterInstance
            .currencyApiService
            .getCurrencyCodes()
    }
    private suspend fun getListOfCurrencyNamesApi() {

        getCurrencyCodes()
        //here you update the change 
        _listOfNames.emit(
            response.supported_codes.map { it[1] }
        )
    }
}

@Composable
fun CurrencyConverter() {
    val myViewModel: CurrencyConverterViewModel = viewModel()
    //Here every time you call your getCurrencyCodes function and issue _listOfNames your composable will recompose and update the data.
    val listOfNames by myViewModel.listOfNames.collectAsState()
}
0
Tenfour04 On

Two ways to do this. (1) is to publish your data in reactive type emitter like LiveData or a hot Flow so it can be collected as State in your Composeable. (2) is to not publish your data but instead only expose a suspend function that can be turned into state in the Composable using produceState.


If you publish your data via
Your data in your ViewModel needs to be published via a LiveData, SharedFlow, or StateFlow, you will use observeAsState() or collectAsState() in your composable to convert it into State.

(Alternatively, you could use MutableState in your ViewModel directly so the Composable can consume it directly.)

If you use a Flow, you can do this with the flow { } builder and stateIn to simplify your code quite a bit.

class CurrencyConverterViewModel : ViewModel() {

    val listOfNames = flow<List<String>> {
        val response = CurrencyConverterInstance
            .currencyApiService
            .getCurrencyCodes()
        emit(response.supported_codes.map { it[1] })
    }.stateIn(viewModelScope, emptyList())

}
@Composable
fun CurrencyConverter() {

    val myViewModel: CurrencyConverterViewModel = viewModel()
    val currencyListOfNames = myViewModel.listOfNames.collectAsState()

}

If you expose a suspend function, it can be converted to State using produceState.

class CurrencyConverterViewModel : ViewModel() {

    suspend fun getListOfCurrencyNamesApi(): List<String> {
        val response = CurrencyConverterInstance
            .currencyApiService
            .getCurrencyCodes()

        return response.supported_codes.map { it[1] }
    }

}
@Composable
fun CurrencyConverter() {

    val myViewModel: CurrencyConverterViewModel = viewModel()
    val currencyListOfNames = produceState<List<String>>(initialValue = emptyList()) { 
        value = myViewModel.getListOfCurrencyNamesApi()
    }

}