I'm trying to refactor my synchronization procedure with rxJava support.
But I've faced a strange (for me) error.
Initially, I execute 'sync' procedure. Then in 'onCompleted' I execute syncPart2. It's the same procedure (but with others nodes to sync)
In the 'syncPart2' I get 'error=DatabaseError: Permission denied'. With database rules everything ok, this error appears on the different nodes (and current sync works fine).
Basically, I have 16 nodes to sync one by one, exactly in specific order. Maybe I've chosen wrong Rx operation to do that? By the way, if I use only one 'concat' everything ok! But I have more than 9 (max size of 'concat' args) nodes to sync.
public class RxFirebaseDatabase {
#NonNull
public static Observable<DataSnapshot> observeSingleValueEvent(#NonNull final Query query) {
return Observable.create(new Observable.OnSubscribe<DataSnapshot>() {
#Override
public void call(final Subscriber<? super DataSnapshot> subscriber) {
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(dataSnapshot);
subscriber.onCompleted();
}
}
#Override
public void onCancelled(DatabaseError error) {
if (!subscriber.isUnsubscribed()) {
subscriber.onError(new RxFirebaseDataException(error));
}
}
});
}
});
}
}
public static void sync() {
Observable.concat(
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.DELETED_OBJECTS_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.MSI_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.COURSES_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.ALLERGIES_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.PHONES_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.MEDICINES_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.PROFILES_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.ANALYSES_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.DIAGNOSES_NODE))
)
.observeOn(Schedulers.io())
.subscribe(new Subscriber<DataSnapshot>() {
#Override
public void onCompleted() {
syncPart2();
}
#Override
public void onError(Throwable e) {
Log.d(AppConstants.TAG_SYNC, "The error appears: " + e.getMessage());
}
#Override
public void onNext(DataSnapshot dataSnapshot) {
GenericClass genericClass = retrieveInfoAboutNode(dataSnapshot);
if (genericClass.getMyType() == DeletedObject.class) {
handleDeletedObjects(dataSnapshot);
} else if (genericClass.getMyType() == MedicineSchedulerItem.class) {
handleMSI(dataSnapshot);
} else if (genericClass.getMyType() == MedicineCourse.class) {
handleMedicineCourse(dataSnapshot);
} else {
handle(dataSnapshot, genericClass);
}
}
});
}
public static void syncPart2() {
Observable.concat(
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.HOSPITALS_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.RECOMMENDATIONS_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.USER_FILES_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.SPECIALIZATIONS_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.DOCTORS_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.DOCTOR_VISITS_NODE)),
RxFirebaseDatabase.observeSingleValueEvent(getFirebaseReference(AppConstants.FIREBASE_CONSTANTS.PHOTOS_NODE))
)
.subscribe(new Subscriber<DataSnapshot>() {
#Override
public void onCompleted() {
EventBus.getDefault().post(new FirebaseEvents().new SyncFinished().new AllTasksFinished());
}
#Override
public void onError(Throwable e) {
Log.d(AppConstants.TAG_SYNC, "The error appears: " + e.getMessage());
}
#Override
public void onNext(DataSnapshot dataSnapshot) {
GenericClass genericClass = retrieveInfoAboutNode(dataSnapshot);
handle(dataSnapshot, genericClass);
}
});
}
Actually, permissions were not the cause of this issue.
I've had 'InvocationTargetException' because of Realm incorrect thread. But why the error was 'permissions denied' is still puzzle for me.
Related
I was looking at the example of an android app coded in MVP (link here). But now I want to recode the given RxJava1 code in the tutorial to an RxJava2 code. However, I am having trouble with it especially unSubscribe() and isUnSubscribed(). I tried to convert it and I will share my attempt.
RxJava1 Code:
public void doLogin(AuthCredentials credentials) {
cancelSubscription();
subscriber = new Subscriber<Account>() {
#Override public void onCompleted() {
if (isViewAttached()) {
getView().loginSuccessful();
}
}
#Override public void onError(Throwable e) {
if (isViewAttached()) {
getView().showError();
}
}
#Override public void onNext(Account account) {
eventBus.post(new LoginSuccessfulEvent(account));
}
};
// do login
accountManager.doLogin(credentials)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
private void cancelSubscription() {
if (subscriber != null && !subscriber.isUnsubscribed()) {
subscriber.unsubscribe();
}
}
This is my attempt on RxJava2 Code:
public void doLogin(AuthCredentials credentials) {
cancelSubscription();
subscriber = new Subscriber<Account>() {
#Override public void onSubscribe(Subscription s) {
// do login
accountManager.doLogin(credentials)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
#Override public void onCompleted() {
if (isViewAttached()) {
getView().loginSuccessful();
}
}
#Override public void onError(Throwable e) {
if (isViewAttached()) {
getView().showError();
}
}
#Override public void onNext(Account account) {
eventBus.post(new LoginSuccessfulEvent(account));
}
};
}
private void cancelSubscription() {
//isUnsubscribed and unsubscribe doesnt work anymore
}
I am fairly new to the concept of RxJava. If anyone can point out my mistakes and guide me that would be great. :)
I apologize for the late answer. Have been extremely busy. As stated by #akarnokd there have been made a lot of changes in RxJava2 as compared to RxJava1. For those who interested please look at this video.
As far as the above question is concerned, we can achieve the same by using DisposableObservables
Here is the answer to the above question. I have tested it and it works.
public void doLogin(AuthCredentials credentials) {
myDisposableObserver = new DisposableObserver<Account>() {
#Override
public void onNext(Account account) {
eventBus.post(new LoginSuccessfulEvent(account));
}
#Override
public void onError(Throwable e) {
if (isViewAttached()) {
getView().showError();
}
}
#Override
public void onComplete() {
if (isViewAttached()) {
getView().loginSuccessful();
}
}
};
// do login
accountManager.doLogin(credentials)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
private void cancelSubscriptionToMyPrescriptorManager() {
if (myDisposableObserver != null && !myDisposableObserver.isDisposed()) {
myDisposableObserver.dispose();
}
}
I'm trying to run a long running task that might fail for some objects in a list I tried retry but it resubscribes to the entire list of observables. I can do nested subscriptions but it seems wrong. Is there any better solution than nesting subscriptions?
Here is my implementation:
public Observable<ReportItemModel> deferReports() {
return Observable.defer(new Callable<ObservableSource<? extends ReportItemModel>>() {
#Override
public ObservableSource<? extends ReportItemModel> call() throws Exception {
return Observable.fromIterable(getReports())
.map(new Function<Report, ReportItemModel>() {
#Override
public ReportItemModel apply(Report report) throws Exception {
return report.getReport();
}
});
}
});
}
reportFactory.deferReports()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.retry()
.subscribe(new Observer<ReportItemModel>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(ReportItemModel value) {
Log.d(TAG,value.toString());
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
I've an Observable something like this:
#GET("endpoint")
Observable<Something> getSomething();
and Subscriber like this
Subscriber<Something> somethingSubscriber = new Subscriber<Something>() {
public void onCompleted() {
}
public void onError(Throwable e) {
//handle exceptions
}
public void onNext() {
//do something
}
In my OnClickListener associated with a button, i make a subscription
getSomething()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(somethingSubscriber);
If i don't have an internet connection, onError is called and i do some exception handling. when I press the button again (assume i want to retry), the callback methods do not get called.
I want that onNext / onError callbacks get called everytime I press the button.
There is extention for RxJava. It has a lot of "cool tools", but for handling retrofit errors you can use ResponseOrError class.
So in you case it would looks like:
final PublishSubject<Object> clickSubject = PublishSubject.create();
final Observable<ResponseOrError<Something>> responseOrErrorObservable = clickSubject
.flatMap(new Func1<Object, Observable<ResponseOrError<Something>>>() {
#Override
public Observable<ResponseOrError<Something>> call(Object o) {
return getSomething()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(ResponseOrError.<Something>toResponseOrErrorObservable());
}
})
.replay(1)
.refCount();
final Observable<Throwable> error = responseOrErrorObservable
.compose(ResponseOrError.<Something>onlyError())
.subscribe(new Action1<Segment>() {
#Override
public void call(Throwable throwable) {
// what to do on error, some toast or what ever yu need
}
});
final Observable<UserInfoResponse> success = responseOrErrorObservable
.compose(ResponseOrError.<Something>onlySuccess())
.subscribe(new Action1<Something>() {
#Override
public void call(Something some) {
// code what to do on success
}
});
And now, into onClick you just need to put clickSubject.onNext(null)
.replay(1).refCount(); needed because there are 2 Observables that uses responseOrErrorObservable, so without it retrofit request will "happens" two times.
You are reusing the same Subscriber. Once you get the onError or a result (so it completes) the subscriber is unsubscribed. Try to pass every time a new subscriber.
use this code
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getSomething()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Something>() {
#Override
public void call(Something something) {
//do something
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
//handle exceptions
}
},
new Action0() {
#Override
public void call() {
}
});
}
});
Addition
or
replace this
Subscriber<Something> somethingSubscriber = new Subscriber<Something>() {
public void onCompleted() {
}
public void onError(Throwable e) {
//handle exceptions
}
public void onNext() {
//do something
}
};
to
Subscriber<String> somethingSubscriber = new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String s) {
}
};
In my Case onNext() and onError() methods are not getting called because of my model class wrong parsing, I was taking a double object as Integer so NumberFormatException was thrown and nothing was happening after getting the result from retrofit.
I have the following code. It's basically an attempt to send all data from a specific SQLite table to DynamoDB:
Observable.create(new Observable.OnSubscribe<Area>() {
#Override
public void call(Subscriber<? super Area> subscriber) {
try {
for (Area item : areaDao.listAll()) {
subscriber.onNext(item);
}
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
}).flatMap(new Func1<Area, Observable<Area>>() {
#Override
public Observable<Area> call(Area area) {
dynamoDBMapper.save(area);
return Observable.just(area);
}
}).observeOn(
AndroidSchedulers.mainThread()
).doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.w("AreaHandler", "Could not upload area", throwable);
}
}).doOnCompleted(new Action0() {
#Override
public void call() {
Toast.makeText(ctx, R.string.toast_upload_successful, Toast.LENGTH_SHORT).show();
}
}).subscribeOn(
Schedulers.io()
).subscribe(new Action1<Area>() {
#Override
public void call(Area area) {
areaDao.delete(area.getId());
}
});
I'm trying to run it on an emulator with disabled Internet connectivity, and what happens is that the Dynamo client does a couple of (failed) retries, then an exception is thrown and it crashes the app. From what I read in the docs, the exception should be swallowed by doOnError instead of being let out to the wild and killing the process.
What am I missing?
You are grabbing the error in the wrong place. doOnError is for side effects. It does not handle the error.
Option 1. Pass in two Action1
Observable.just(1, 2, 3)
.subscribe(
new Action1<Integer>() {
#Override
public void call(Integer integer) {
System.out.println(integer);
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
System.err.print(throwable);
}
});
Option 2: Pass in an Observer
Observable.just(1, 2, 3)
.subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
System.err.print(throwable);
}
#Override
public void onNext(Integer integer) {
System.out.println(integer);
}
});
The user input a url from the ui,and then my presenter validate it,if it is valid,insert it into the db,else show error;
my solution looks like:
#Override
public void addFeed(final String uri) {
Observable.create(new Observable.OnSubscribe<Boolean>() {
#Override
public void call(Subscriber<? super Boolean> subscriber) {
//may contain some database query,if it has,how should i do ?
subscriber.onNext(uri.startsWith(FeedFetcher.FEED_SCHEME) && !uri.startsWith(FeedFetcher.HTTP_SCHEME));
subscriber.onCompleted();
}
}).doOnNext(new Action1<Boolean>() {
#Override
public void call(Boolean aBoolean) {
//insert into db
if (aBoolean)
mFeedModel.add(uri);
}
}).subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean aBoolean) {
if (aBoolean){
mFeedInputView.hideInputError();
}else{
mFeedInputView.showInputError(R.string.url_format_error);
}
}
});
}
**updated **
if (uri.startsWith(FeedFetcher.FEED_SCHEME) || uri.startsWith(FeedFetcher.HTTP_SCHEME)) {
mFeedInputView.hideInputError();
mChannelModel.isChannelExists(uri)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap(new Func1<Boolean, Observable<RssChannel>>() {
#Override
public Observable<RssChannel> call(Boolean aBoolean) {
if (aBoolean)
return mChannelModel.add(uri);
return Observable.empty();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<RssChannel>() {
#Override
public void call(RssChannel channel) {
mFeedInputView.addUrlSuccess();
}
});
} else {
mFeedInputView.showInputError(R.string.url_format_error);
}
but i think putting if(aBoolean) in the flapmap is not a pretty solution..
How about:
public void addFeed(String uri) {
Observable.just(uri)
.first(new Func1<String, Boolean>() {
#Override
public Boolean call(String uri) {
return uri.startsWith(FeedFetcher.FEED_SCHEME) && !uri.startsWith(FeedFetcher.HTTP_SCHEME);
}
})
.subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
mFeedInputView.hideInputError();
}
#Override
public void onError(Throwable e) {
mFeedInputView.showInputError(R.string.url_format_error);
}
#Override
public void onNext(String s) {
mFeedModel.add(uri);
}
});
}
The first() operator will filter out invalid uris it receives based on your filter rule and throw a NoSuchElementException if no valid uri is found (in your case, if the one passed as parameter is invalid).
onCompleted() will be called only if the uri is valid so you can be safe to call hideInputError() there. onError() will be called instead of onCompleted() only if an error occurs so it is safe to do the error handling there. Finally, onNext() will be called when the uri is valid since the first() operator is ignoring invalid uris so it is safe to save it there.
Note that I removed the final attribute from your method declaration. There is no need for it here since you are passing the uri itself down the chain with just().
Also worth noting that you don't have to use RxJava for the sake of it. In your case, if you don't need to run this asynchronously, this is probably a much cleaner solution:
public void addFeed(String uri) {
if (uri.startsWith(FeedFetcher.FEED_SCHEME) && !uri.startsWith(FeedFetcher.HTTP_SCHEME)) {
mFeedInputView.hideInputError();
mFeedModel.add(uri);
} else {
mFeedInputView.showInputError(R.string.url_format_error);
}
}
I much rather has this code in more functional way.
Observable.create(new Observable.OnSubscribe<Boolean>())
.map(b -> b = false)
.doOnNext(uri.startsWith(FeedFetcher.FEED_SCHEME) && !uri.startsWith(FeedFetcher.HTTP_SCHEME))
.map(b -> {
mFeedModel.add(uri);
b=true;
return b;
})
.subscribe(b-> {
if(!b){
mFeedInputView.showInputError(R.string.url_format_error);
}
});