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
Related
I am trying to save the data on the server to the local database with Room. Since these tables are related to each other, I want the insertion to be done in order.I listen to these operations with RxJava. For example i have school's and season's tables and and that's how I add the data:
fun insertAllSchools(vararg schools: School):Completable=dao.insertAll(*schools)
fun insertAllSeasons(vararg seasons: Season):Completable=dao.insertAll(*seasons)
When I create a separate method for each table, the insertion process is done, but I have to write a disposable method for each of them. Like this:
fun insertAllSchools(allData:ResponseAll){
if(allData.schoolList!=null){
disposable.add(
repositorySchool.insertAll(*allData.schoolList.toTypedArray())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableCompletableObserver(){
override fun onComplete() {
Log.d(TAG,"OnComplete")
}
override fun onError(e: Throwable) {
Log.e(TAG,"Error"+e.localizedMessage)
}
})
)
}
}
When one is complete, I call the other method, but this time there is a lot of unnecessary code.
I have tried different methods to combine these completable methods and work sequentially, but it does not add to the database even though it appears in the logs.
For example, I tried to combine it this way:
if(allData.schoolList!=null){
mObservable = Observable.fromArray(
repositorySchool.clearAllData(),
repositorySchool.insertAll(*allData.schoolList.toTypedArray())
)
disposable.add(
mObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<Completable>() {
override fun onComplete() {
Log.d(TAG,"onComplete")
isDataLoad.value = true
}
override fun onNext(t: Completable) {
Log.d(TAG,"onNext"+t)
}
override fun onError(e: Throwable) {
Log.e(TAG,"onError")
}
})
)
}
I do not receive any errors. How can I combine these completable methods and make them work sequentially. Thanks!
Edit(Solution): It works like this:------------>
if(allData.schoolList!=null) {
disposable.add(
repositorySchool.clearAllData()
.andThen(Completable.defer { repositorySchool.insertAll(*allData.schoolList.toTypedArray()) })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableCompletableObserver() {
override fun onComplete() {
isDataLoad.value = true
}
override fun onError(e: Throwable) {
Log.e(TAG,""+e.localizedMessage)
}
})
)
}
I disagree with using doOnComplete(). In that case your not combining the Completables into a single Completable event that you can observe. What you probably want is something like doThingA().andThen(Completable.defer(() -> doThingB()) as mentioned in this answer on a similar question.
There is a method called doOnComplete() which you can use to make your second call through a lambda or jack.
Context: My current application uses normal retrofit calls in order to get data from the api. I really wanted to introduce RX into the calls but I don't have much experience with that. I read some things online and none of them show me a simple way to do this. I'll show you what I have.
Purpose: To turn what I have into RXJava
This is my code :
My generic perform call method that I want to convert into RXJava:
fun <T> performCall(call: Call<T>?, callback: OnRequestCallback<T>) {
call?.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
when (response.code()) {
200, 201 -> {
callback.onSuccess(response.body())
}
else -> {
callback.onError(response.errorBody())
}
}
return
}
override fun onFailure(call: Call<T>, t: Throwable) {
callback.onError(null)
Log.d("Network Manager Failure", t.localizedMessage)
}
})
}
Then to have a context from my activity I use this method that calls the perform call method:
fun <T> BaseActivity.callAPI(call: Observable<T>?, onSucceed: (T?) -> Unit, onError: (errorCode: String) -> Unit) {
NetworkManager.instance.performCall(call,
object : NetworkManager.OnRequestCallback<T> {
override fun onSuccess(body: T?) {
onSucceed(body)
}
override fun onError(errorResponseBody: ResponseBody?) {
JSONObject(errorResponseBody?.string()).let {
val errorCode = it.getJSONObject("meta").getString("errorCode")
when (errorCode) {
Errors.DEPRECATED_API_VERSION.name ->
onAppUpdateNeeded()
else -> onError(errorResponseBody)
}
}
}
})
}
Then the BaseActivitt.callApi() is used in every activity that needs api information, I now the use of view models + dagger is better but for now is what I have and I have to keep it.
Can someone show me how to turn this into an RXJava/Kotlin thing?
To be honest I don't like the idea of having these generic handlers. I had to work on a project that was written like that and it wasn't a nice experience: what happens if, for example, you want to handle the Errors.DEPRECATED_API_VERSION in a different way for a call?
Anyway, I would do something like this: from Retrofit return the Observable and then in the place where you need to make the call subscribe to the Observable.
val disposable = service
.yourApi()
.map { value ->
MyCommand.SuccessCommand(value)
}
.onErrorResumeNext { ex: Throwable -> YourObservableThatEmitsTheErrorCommandOrTheOnAppUpdateNeededCommand() }
.subscribe { command: MyCommand ->
when (command) {
is MyCommand.SuccessCommand -> {
}
is MyCommand.ErrorCommand -> {
}
is MyCommand.AppUpdateNeededCommand -> {
}
}
}
The command can be implemented something like
sealed class MyCommand {
class SuccessCommand<T> (value: T): MyCommand()
class ErrorCommand (val ex: Exception): MyCommand()
object AppUpdateNeededCommand: MyCommand()
}
So my goal like the the title says is to pass API response results throw an onSuccess.run() method to a fragment. Just to give some context, I start to do the method like this in a Manager class for example:
override fun callUser(onSuccess:Runnable, onFailure:Runnable){
NetworkManager.instance.performCall(NetworkManager.REGISTRATION.verifyUser(id),
object : NetworkManager.OnRequestCallback<UserInfoResponse> {
override fun onSuccess(body: UserInfoResponse?) {
body?.data?.let {
onSuccess.run()
}
}
override fun onError(errorBody: String?) {
onFailure.run()
}
})
}
Then I go to a fragment and call the method above like this:
objectManager.callVerifyAdvisor(
Runnable {[On Success stuff },
Runnable {[On Error]}
}
The problem is that, although I can decide in the fragment the actions I want to do in the onSuccess() and onFailure() methods, I cant get the API Results to that fragment by doing this way.
So my ideia is to do something like [I used comments to specify the sections that matter]:
NetworkManager.instance.performCall(NetworkManager.REGISTRATION.verifyUser(id),
object : NetworkManager.OnRequestCallback<UserInfoResponse> {
override fun onSuccess(body: UserInfoResponse?) {
body?.data?.let {
it.userName // I get this from api response
onSuccess.run()
}
}
override fun onError(errorBody: String?) {
onFailure.run()
}
})
}
Then on my fragment I want something like this:
objectManager.callVerifyAdvisor(
Runnable {[On Success stuff }, //receive here the it.userName
Runnable {[On Error]}
}
Can someone give any ideia how to do this? Side note -> I put kotlin on the tag because this has some kind of functional stuff.
You just need to replace you onSuccess from Runnable to your custom functional type callback:
override fun callUser(onSuccess: (String) -> Unit, onFailure: Runnable) {...
Then pass userName to callback:
NetworkManager.instance.performCall(NetworkManager.REGISTRATION.verifyUser(id),
object : NetworkManager.OnRequestCallback<UserInfoResponse> {
override fun onSuccess(body: UserInfoResponse?) {
body?.data?.let {
onSuccess.invoke(it.userName) // Pass userName
}
}
override fun onError(errorBody: String?) {
onFailure.run()
}
})
}
And then you can get userName from this callback:
objectManager.callVerifyAdvisor(
{userName -> } // Here you get your result
Runnable {[On Error]}
}
I have the following code, that does one single call, gets the result of the call, which is a boolean, then makes the second call if the result is false.
private fun linkEmailAndTextTogether(contactPhoneNumber: ContactPhoneNumbers,phoneNumber : PhoneNumber) {
val single = SingleOnSubscribe<Boolean> {
contactPhoneNumber.doesEmailContactExist(phoneNumber)
}
Single.create(single)
.subscribeOn(Schedulers.io())
.subscribeWith(object : SingleObserver<Boolean> {
override fun onSuccess(phoneNumberDoesExist: Boolean) {
if (!phoneNumberDoesExist) {
val completable = CompletableOnSubscribe {
contactPhoneNumber.linkEmailAndTextTogether(phoneNumber)
}
compositeDisposable.add(Completable.create(completable)
.subscribeOn(Schedulers.io())
.subscribe())
}
}
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onError(e: Throwable) {
Timber.e(e,e.localizedMessage)
}
})
}
It seems like there should be a more elegant way to do this in some kind of chain.
you could use the flatMap operator - the downside is that you won't know if the first or the second failed.
Single.just(phoneNumber)
.subscribeOn(Schedulers.io())
.map { it -> contactPhoneNumber.doesEmailContactExist(it) }
.flatMap { it ->
if (it) {
return#flatMap contactPhoneNumber.linkEmailAndTextTogether(phoneNumber)
}
Single.just(it)
}.subscribe({}, Throwable::printStackTrace);
This should help.
val single = SingleOnSubscribe<Boolean> {
getSingle()
}
Single.create(single).map({
if (it){
return#map getCompleteable()
}
return#map Completable.complete()
})
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()
}
}