Home > Enterprise >  How can I get data and initialize a field in viewmodel using kotlin coroutines and without a latenit
How can I get data and initialize a field in viewmodel using kotlin coroutines and without a latenit

Time:01-19

I have a common situation of getting data. I use the Kotlin Coroutines.

1 variant:

class SomeViewModel(
    private val gettingData: GetDataUseCase
) : ViewModel() {

    lateinit var data: List<String>

    init {
        viewModelScope.launch {
            data = gettingData.get()
        }
    }
}

2 variant:

class SomeViewModel(
    private val gettingData: GetDataUseCase
) : ViewModel() {

    val data = MutableStateFlow<List<String>?>(null)

    init {
        viewModelScope.launch {
            data.emit(gettingData.get())
        }
    }
}

How can I initialize a data field not delayed, but immediately, with the viewModelScope but without a lateinit or nullble field? And without LiveData, my progect uses Coroutine Flow

I can't return a result of viewModelScope job in .run{} or by lazy {}. I cant return a result drom fun:

val data: List<String> = getData()

fun getData(): List<String> {
    viewModelScope.launch {
        data = gettingData.get()
    }
    return ???
}

Also I can't make suspend fun getData() because I can't create coroutineScope in initialisation'

CodePudding user response:

You have to use SharedFlow with replay 1 (to store last value and replay it for a new subscriber) to implement it.

My sample:

interface DataSource {
    suspend fun getData(): Int
}

class DataViewModel(dataSource: DataSource): ViewModel() {
    val dataField =
        flow<Int> {
            dataSource.getData()
        }.shareIn(viewModelScope, SharingStarted.WhileSubscribed(1000), 1)
}

CodePudding user response:

You're describing an impossibility. Presumably, gettingData.get() is defined as a suspend function, meaning the result literally cannot be retrieved immediately. Since it takes a while to retrieve, you cannot have an immediate value.

This is why apps and websites have loading indicators in their UI.

If you're using Flows, you can use a Flow with a nullable type (like in your option 2 above), and in your Activity/Fragment, in the collector, you show either a loading indicator or your data depending on whether it is null.

Your code 2 can be simplified using the flow builder and stateIn with a null default value:

class SomeViewModel(
    private val gettingData: GetDataUseCase
) : ViewModel() {

    val data = flow<List<String>?> { gettingData.get() }
        .stateIn(viewModelScope, SharingStarted.Eagerly, null)

}

In your Activity or Fragment:

viewLifecycleOwner.lifecycleScope.launch {
    viewModel.data
        .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
        .collect { list ->
            if(list == null) {
                // Show loading indicator in UI
            } else {
                // Show the data
            }
         }
}

If your data loads pretty quickly, instead of making the type nullable, you can just make the default value emptyList(). Then your collector can just not do anything when the list is empty. This works if the data loads quickly enough that the user isn't going to wonder if something is wrong because the screen is blank for so long.

  • Related