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.
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()
}
}
Related
I get a list of the index composition (102 tickers) and I want to find out detailed information about them, but out of 102 queries, no more than 10 are always executed, and the ticker is randomly selected. All requests are executed via retrofit2 using RxJava3. What could be the problem?
Here is the ViewModel code:
var price: MutableLiveData<CompanyInfoModel> = MutableLiveData()
fun getCompanyInfoObserver(): MutableLiveData<CompanyInfoModel> {
return price
}
fun makeApiCall(ticker: String) {
val retrofitInstance = RetrofitYahooFinanceInstance.getRetrofitInstance().create(RetrofitService::class.java)
retrofitInstance.getCompanyInfo(ticker)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getCompanyInfoObserverRx())
}
private fun getCompanyInfoObserverRx(): Observer<CompanyInfoModel> {
return object : Observer<CompanyInfoModel> {
override fun onComplete() {
// Hide progress bar
}
override fun onError(e: Throwable?) {
price.postValue(null)
}
override fun onNext(t: CompanyInfoModel?) {
price.postValue(t)
}
override fun onSubscribe(d: Disposable?) {
// Show progress bar
}
}
}
Here is the initialization of the model:
companyInfoModel = ViewModelProvider(this).get(CompanyInfoViewModel::class.java)
companyInfoModel.getCompanyInfoObserver().observe(this, Observer<CompanyInfoModel> { it ->
if(it != null) {
retrieveList(Helper.companyInfoToStock(it))
}
else {
Log.e(TAG, "Error in fetching data")
}
})
And here is the request method itself:
fun getCompanyInfo(ticker: String) {
companyInfoModel.makeApiCall(ticker)
}
Thank you, Pawel. The problem really turned out to be in the API limit, I changed the provider and everything started working as it should.
I'm just trying to find an answer how to pass the data from Repository to ViewModel without extra dependencies like RxJava. The LiveData seems as a not good solution here because I don't need to proceed it in my Presentation, only in ViewModel and it's not a good practice to use observeForever.
The code is simple: I use Firebase example trying to pass data with Flow but can't use it within a listener (Suspension functions can be called only within coroutine body error):
Repository
fun fetchFirebaseFlow(): Flow<List<MyData>?> = flow {
var ret: List<MyData>? = null
firebaseDb.child("data").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val data = dataSnapshot.getValue<List<MyData>>()
emit(data) // Error. How to return the data here?
}
override fun onCancelled(databaseError: DatabaseError) {
emit(databaseError) // Error. How to return the data here?
}
})
// emit(ret) // Useless here
}
ViewModel
private suspend fun fetchFirebase() {
repo.fetchFirebaseFlow().collect { data ->
if (!data.isNullOrEmpty()) {
// Add data to something
} else {
// Something else
}
}
You can use callbackFlow
#ExperimentalCoroutinesApi
fun fetchFirebaseFlow(): Flow<List<String>?> = callbackFlow {
val listener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val data = dataSnapshot.getValue<List<MyData>>()
offer(data)
}
override fun onCancelled(databaseError: DatabaseError) {
}
}
val ref =firebaseDb.child("data")
reef.addListenerForSingleValueEvent(listener)
awaitClose{
//remove listener here
ref.removeEventListener(listener)
}
}
ObservableField is like LiveData but not lifecycle-aware and may be used instead of creating an Observable object.
{
val data = repo.getObservable()
val cb = object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(observable: Observable, i: Int) {
observable.removeOnPropertyChangedCallback(this)
val neededData = (observable as ObservableField<*>).get()
}
}
data.addOnPropertyChangedCallback(cb)
}
fun getObservable(): ObservableField<List<MyData>> {
val ret = ObservableField<List<MyData>>()
firebaseDb.child("events").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
ret.set(dataSnapshot.getValue<List<MyData>>())
}
override fun onCancelled(databaseError: DatabaseError) {
ret.set(null)
}
})
return ret
}
It is also possible to use suspendCancellableCoroutine for a single result. Thanks to Kotlin forum.
private val searchSubject = PublishSubject.create<Boolean>()
private val compositeDisposable = CompositeDisposable()
fun textChange(){
searSubject.onNext(true)
}
fun getSubject(){
compositeDisposable += searchSubject
.doOnNext {
if (it) showLoading()
}
.switchMap { searchGithubReposObservable() }
.subscribeWith(object : DisposableObserver<List<GithubRepo>>() {
override fun onNext(t: List<GithubRepo>) {
hideLoading()
adapter.items = t
}
override fun onComplete() {
}
override fun onError(e: Throwable) {
hideLoading()
}
})
}
searchGithubReposObservable is the fucntion that returns the Observable<List<GithubRepo>>
I searched the sample code in the github for studying RxJava.
However, I can't understand the above code.
I know that to receive data from PublishSubject, I need to subscribe it.
In the above code, I thought that subscribeWith subscribes searchGithubReposObservable()'s return Observable , but I could get the data from PublishSubject when the textchange() is called.
Why is it possible?
The start of your RX chain you are listening to the publish subject.
compositeDisposable += searchSubject
.doOnNext {
if (it) showLoading()
}
Each time you call method textChange() you push to searchSubject which fires the RX chain all over again trigging the switchmap.
Yes is it possible you can get the data when textchange() method is getting called i have implemented this type of functionality while i was typing text api got called on textchange and i received data Below code.
I have written please check
autocompletetextview.debounce(500L, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.filter { it.trim().isNotEmpty() || it.isEmpty() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Observable.just(callapi here )
}
.subscribe({
it.subscribe({ serviceResponse ->
if (serviceResponse.meta.status == KeyUtils.HTTP_SUCCESS ||
serviceResponse.meta.status == KeyUtils.STATUS_META_ERROR) {
setSuccessResponse(serviceResponse, true)
} else {
setSuccessResponse(serviceResponse, false)
}
}, { throwable ->
setErrorResponse(throwable)
}).collect()
I am faced with a problem and I don't know why.
Only the original thread that created a view hierarchy can touch its views
in my fragment, I have a method OnUserImageClicked
override fun OnUserNumberClicked(photo: UserPicture) {
subscriptions.add(
mRepository.userPicture()
.subscribeOn(Schedulers.io())
.subscribe {
userListener.updateUserNumber()
}
)
}
userListener is an interface in other presenter which have these following method
interface UserListener {
fun updateUserNumber()
}
private var userListener = object : UserListener {
override fun updateUserPicture() {
fetchUserNumber()
}
}
and in the presenter I have the following method fetchNumberOfPictureSelected()
fun fetchUserNumber(): Int {
subscriptions.add(mRepository.getUserNumber()
.subscribeOn(Schedulers.io())
.subscribe (
{ number -> view?.updateNumber(number) },
{ // ErrorCode here }
)
)
}
then my fragment I have this following code :
userNumberUpdate.text = number
So on my emulator, when I Click on "OnUserNumberClicked" method, the number is updated but the app crash after.
Can you help me ?
You need to observe UI calls on the main thread using AndroidSchedulers.mainThread().
override fun OnUserNumberClicked(photo: UserPicture) {
subscriptions.add(
mRepository.userPicture()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
userListener.updateUserNumber()
}
)
}
I am new to using rxjava and I am trying to run a function in background using rxjava2 but the method is not called the code I am using is given below let me know if its the right way to execute a function in background:
Observable.fromCallable<OrderItem>(Callable {
saveToDb(existingQty, newOty, product_id)
}).doOnSubscribe {
object : Observable<OrderItem>() {
override fun subscribeActual(emitter: Observer<in OrderItem>?) {
try {
val orderItem = saveToDb(existingQty, newOty, product_id)
emitter?.onNext(orderItem)
emitter?.onComplete()
} catch (e: Exception) {
emitter?.onError(e)
}
}
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).doOnSubscribe {
object : Observer<OrderItem> {
override fun onComplete() {
}
override fun onNext(t: OrderItem) {
}
override fun onError(e: Throwable) {
}
override fun onSubscribe(d: Disposable) {
}
}
}
You are dong it wrong way. doOnSubscribe() operator is called when observable is subscribed using subscribe() method and you haven't subscribed the observable using subscribe() method.
You have called saveToDb method in callable, then why are you calling it in doOnSubscribe? it doesn't make sense.
You should have written following code:
Observable.fromCallable { saveToDb(existingQty, newOty, product_id) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ orderItem ->
// set values to UI
}, { e ->
// handle exception if any
}, {
// on complete
})
to work with your logic.
DoOnSubscribe means "do when someone subscribe to it". But there is no subscribe in your code. Maybe you want to use subsribe instead of doOnSubscribe