1

I am writing following code snippet to fetch list of saved food from firebase database and then using that list, I am again fetching individual food details from firebase database.

Following code working fine, except i am unable to figure out how to let second flatMap know that emission of first flatMap has finished(All food list has been processed). So I am unable to call onCompleted() method hence unable to detect when whole process finishes.

Have a look at comments in following snippet:

Observable.create<List<PersonalizedFood>> {

            FirebaseDTDatabase.getSavedDietFoodQuery(user.uid).addListenerForSingleValueEvent(object : ValueEventListener {
                override fun onCancelled(p0: DatabaseError?) {

                }

                override fun onDataChange(p0: DataSnapshot?) {
                    val list = ArrayList<PersonalizedFood>()
                    p0?.let {
                        for (dateObject in p0.children) {
                            for (foodItem in dateObject.children) {
                                val food = foodItem.getValue(FBPersonalizedFood::class.java) as FBPersonalizedFood
                                list.add(PersonalizedFood(food))
                            }
                        }
                    }
                    it.onNext(list)
                    it.onCompleted()
                }
            })
        }.subscribeOn(Schedulers.io()).flatMap {
            Observable.from(it) // returning a Observable that emits items of list ("it" is the list here) 
        }.observeOn(Schedulers.io()).flatMap {
        // How does this flatMap know that emission of all item has been finished so that onCompleted() method could be called.
            personalizedFood ->

            Observable.create<Boolean>{
                FirebaseDTDatabase.getFoodListReference(personalizedFood.foodId).addListenerForSingleValueEvent(object :ValueEventListener{
                    override fun onCancelled(p0: DatabaseError?) {
                        it.onError(p0?.toException())
                    }

                    override fun onDataChange(p0: DataSnapshot?) {
                        if(p0 != null) {
                            val food = p0.getValue(FBFood::class.java)!!
                            val repo = LocalFoodRepository()
                            doAsync {
                                repo.insertFood(this@LoginActivity, Food(food.foodId, food.foodName, food.foodDesc))
                                repo.insertServingDetails(this@LoginActivity, food.servingList.map { it.component2() })
                                repo.saveFood(this@LoginActivity, personalizedFood)
                                it.onNext(true)
                            }

                        }else {
                            it.onNext(false)
                        }
                    }

                })
            }
        }.observeOn(Schedulers.io()).doOnCompleted{
            dismissProgressDialog()
            finish()
        }.doOnError{
            it.printStackTrace()
            dismissProgressDialog()
            finish()
        }.subscribe()

Thanks.

Sachin Chandil
  • 17,133
  • 8
  • 47
  • 65
  • Are you using fireabse? There are some 3rd party RxFirebase can give you a nice wrapper for Firebase database. – Phoenix Wang Jul 19 '17 at 17:22
  • @PhoenixWang That's different matter. actually i am learning so want to know how to achieve that without using any third party wrapper. And i am not fan of using third party lib if thats easy enough to do it by yourself.. – Sachin Chandil Jul 19 '17 at 17:33
  • Well, actually you don't need to. In the first observable you fetch all the PersonalizedFood items and emit them as list then complete the stream. Then you transform it to a set of items, each of them is processed inside second flatMap. The key point is that every onCompleted is passed down the stream, so second flatMap "knows" that there will be no more elements and completes itself. I would suggest to review code-safety and consistency, because some points look wrong to me. – MightySeal Jul 19 '17 at 18:08

1 Answers1

2

The Observable from the flatMap knows "when to all of the items have been finished" when all of the observables emitted by it have called onCompleted(). The second flatMap in your code never calls onCompleted() because none of the observables it creates call onCompleted().

You should call onCompleted() in your onDataChange() method. Since each of the observables created in the flatMap only emit one item, it can be called directly after the onNext() method:

override fun onDataChange(p0: DataSnapshot?) {
    if(p0 != null) {
        val food = p0.getValue(FBFood::class.java)!!
        val repo = LocalFoodRepository()
        doAsync {
            repo.insertFood(this@LoginActivity, Food(food.foodId, food.foodName, food.foodDesc))
            repo.insertServingDetails(this@LoginActivity, food.servingList.map { it.component2() })
            repo.saveFood(this@LoginActivity, personalizedFood)
            it.onNext(true)
            it.onCompleted()
        }
    } else {
        it.onNext(false)
        it.onCompleted()
    }
}
Bryan
  • 14,756
  • 10
  • 70
  • 125
  • This does not fulfil my requirement. I want to finish activity when all data has been fetched. That's why i cant call `onComplete` on `onDataChange()`. – Sachin Chandil Jul 20 '17 at 05:32
  • @chandil03 The would be the `doOnCompleted()` method, no? You call `finish()` within that method, so adding `onComplete()` should trigger the activity to finish. – Bryan Jul 20 '17 at 12:35
  • I know that , if you check my code carefully i have added `finish()` method call in `doOnCompleted()`. if i call `onComplete()` where you suggest me, `doOnComplete method will be called as many times as much items are emitted by observable`. So `finish()` will be called multiple times hence will result in app crash as activity will be null. – Sachin Chandil Jul 20 '17 at 16:11
  • @chandil03 That is incorrect; `doOnCompleted()` will only be called as many times as the `flatMap` observable calls `onCompleted()`. The `flatMap` method creates an `Observable` for each `List` passed to it, then merges those into a single `Observable`. That single observable only calls `onCompleted()` after all of the original observables have called `onCompleted()`. The `onOnCompleted()` method should only be called once. Have you tried it? – Bryan Jul 20 '17 at 16:17
  • Cool it worked.. , But still scratching my heads to figure out, `onComplete()` is being called multiple times but why it is triggered only once? – Sachin Chandil Jul 20 '17 at 17:32
  • 1
    @chandil03 Because `flatMap` creates multiple observables, and then merges them into a *single* observable. That single observable calls `onComplete()` only after each of the merged observables calls `onComplete()`. – Bryan Jul 20 '17 at 18:07