Kotlin - wait for multiple LiveData to be observe before running function
Asked Answered
L

2

10

I am using a viewModel to pull livedata from a room data base. I have 2 LiveData that I pull from my viewModel and then I will run a function to pull data from my server. I need both values to be set before running my function that will get info from my server because these values are part of the body of the post.

This is part of my activity

var locationSeleted:Long=0L

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView<ActivityBinding>(this,R.layout.activity)

        //This is to get the viewmodel
        val application = requireNotNull(this).application
        val dataSource = Database.getInstance(application).DatabaseDao
        val viewModelFactory = ViewModelFactory(dataSource, application)
        val ViewModel = ViewModelProviders.of(this,viewModelFactory).get(ViewModel::class.java)

        binding.ViewModel = ViewModel
        binding.setLifecycleOwner (this)

        //Get location from livedata
        ViewModel.Location.observe(this, Observer {location ->

            if (location == null){
                //do something
            }
            else{
                locationSeleted = location.locationId
            }

        })

        //listens to the live data of the products value
        ViewModel.Products.observe(this, Observer{products ->
            
            if (products == null){
                //do something
            }
            else{
                myfunction(products)
            }

        })

This is part of my viewmodel

class ViewModel(val database: DatabaseDao, application: Application) : AndroidViewModel(application) {


    //This gets the location selected
    var Location: LiveData<LocationSelect> = database.get_location()

    //This line gets the products
    var Products: LiveData<Array<Products>> = database.get_products()


My current code works but I can imagen that if Location LiveData is delayed then myfunction will run with no location and the Post call will false. My question is how can I call myfunction when both location and products set from the viewModel? or is there a better way of doing this.

Thanks you

Lurette answered 21/8, 2020 at 19:5 Comment(0)
M
11

Edit: This can be done concisely using intermediate Flows, since Flows have convenient operators like combine.

val locationAndProductsLiveData: LiveData<Pair<LocationSelect, Array<Products>>> = 
    location.asFlow().combine(products.asFlow()) { location, products ->
        location to products
    }.asLiveData()

You can use a MediatorLiveData so you only have to observe one thing, and it fires only when both items are received, and on every change after that.

In the ViewModel:

val locationAndProductsLiveData: LiveData<Pair<LocationSelect, Array<Products>>> = 
    object: MediatorLiveData<Pair<LocationSelect, Array<Products>>>() {
        var location: LocationSelect? = null
        var products: Array<Products>? = null
        init {
            addSource(location) { location ->
                this.location = location 
                products?.let { value = location to it }
            }
            addSource(products) { products ->
                this.products = products 
                location?.let { value = it to products }
            }
        }
    }

In your Activity:

viewModel.locationAndProductsLiveData.observe(this) { (location, products) ->
    locationSelected = location.Id
    myFunction(products)
}
Manifold answered 21/8, 2020 at 20:51 Comment(4)
addSource() is not working. Is there a library I need to add?Lurette
Oops, you have to specify the type of the class. Not sure why it isn't inferred. See edit.Manifold
Thank you that worked! One question I have is what is the following code doing? products?.let { value = location to it } And location?.let { value = it to products } I understand that we are setting the local values to the livedata. but I don't understand the syntax. Could you explain?Lurette
?.let means run the let lambda only if the receiver is not null. So the value is only updated if both sources are known to have non-null data. to between two items is an infix function to create a Pair of them.Manifold
H
0

You can observe with live data. In your viewmodel:

val _weHaveAllTheData = MutableLiveData(pairToShowSpinner)
val weHaveAllTheData: LiveData<Pair<Boolean, Boolean>> = _weHaveAllTheData

fun updateTheData(int firstOrSecond: Int) { 
    val pair = _weHaveAllTheData.value
    if (firstOrSecond = 1) {
       
       val secondPair = pair?.second as Boolean
       val pairInObservable = Pair(true, secondPair)
       _weHaveAllTheData.value = pairInObservable
    } else {
       val firstPair = pair?.first as Boolean
       val pairInObservable = Pair(firstPair, true)
       _weHaveAllTheData.value = pairInObservable 
    }

}

In your activity:

    //Get location from livedata
    ViewModel.Location.observe(this, Observer {location ->

        if (location == null){
            //do something
        }
        else{
            locationSeleted = location.locationId
        }
        ViewModel.updateTheData(1)


    })

//listens to the live data of the products value
    ViewModel.Products.observe(this, Observer{products ->
        
        if (products == null){
            //do something
        }
        else{
            ViewModel.updateTheData(2)
            
        }

    })

    ViewModel.weHaveAllTheData.observe(lifecycleOwner, Observer {
        val positivePair = Pair(first = true, second = true)
        if (it == positivePair)
            myfunction(ViewModel.Products)
    })
Harebrained answered 21/8, 2020 at 19:58 Comment(2)
To start the MutableLiveData you use the value pairToShowSpinner. What do I need to initialize the MutableLiveData?Lurette
_weHaveAllTheData = MutableLiveData(Pair(true, true))Harebrained

© 2022 - 2024 — McMap. All rights reserved.