I have a problem with Flowables and adding them to the compositeDisposables.
I want to switch from an Observable to a Flowable as the operation might emit 1000 or more values. Im somewhat unexperienced with rxjava2 so please forgive me if that question is stupid :)
So far I used the observable like this:
public Observable<String> uploadPictureRx(String path)
{
return Observable.create(new ObservableOnSubscribe<String>()
{
#Override
public void subscribe(ObservableEmitter<String> e) throws Exception
{
Uri file = Uri.fromFile(new File(path));
String segment = file.getLastPathSegment();
UploadTask uploadTask = reference.child("SomeChild").child(segment).putFile(file);
uploadTask.addOnFailureListener(new OnFailureListener()
{
#Override
public void onFailure(#NonNull Exception exception)
{
e.onError(exception);
}
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>()
{
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot)
{
//noinspection VisibleForTests
downloadUrl = taskSnapshot.getDownloadUrl();
String url = downloadUrl.getPath();
e.onNext(url);
e.onComplete();
}
}).addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>()
{
#Override
public void onProgress(UploadTask.TaskSnapshot taskSnapshot)
{
//noinspection VisibleForTests
long bytes = taskSnapshot.getBytesTransferred();
String bytesS = String.valueOf(bytes);
e.onNext(bytesS);
}
});
}
});
}
and called the method like this:
private void uploadPicToFireBaseStorage(String path)
{
compositeDisposable.add(storageService.uploadPictureRx(path)
.subscribeOn(Schedulers.io())
.observeOn(mainScheduler)
.subscribeWith(new DisposableObserver<String>()
{
#Override
public void onNext(String s)
{
String ss = s;
System.out.println(ss);
}
#Override
public void onError(Throwable e)
{
e.printStackTrace();
}
#Override
public void onComplete()
{
view.displayToast("Picture Upload completed");
}
})
);
}
This works fine! However when I try to do the same with a Flowable instead of observable it wont compile:
public Flowable<String> uploadPictureRx(String path)
{
return Flowable.create(new FlowableOnSubscribe<String>()
{
#Override
public void subscribe(FlowableEmitter<String> e) throws Exception
{
Uri file = Uri.fromFile(new File(path));
String segment = file.getLastPathSegment();
UploadTask uploadTask = reference.child("somechild").child(segment).putFile(file);
uploadTask.addOnFailureListener(new OnFailureListener()
{
#Override
public void onFailure(#NonNull Exception exception)
{
e.onError(exception);
}
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>()
{
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot)
{
//noinspection VisibleForTests
downloadUrl = taskSnapshot.getDownloadUrl();
String url = downloadUrl.getPath();
e.onNext(url);
e.onComplete();
}
}).addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>()
{
#Override
public void onProgress(UploadTask.TaskSnapshot taskSnapshot)
{
//noinspection VisibleForTests
long bytes = taskSnapshot.getBytesTransferred();
String bytesS = String.valueOf(bytes);
e.onNext(bytesS);
}
});
}
}, BackpressureStrategy.BUFFER);
}
The Error is:
Inferred type 'E' for type parameter 'E' is not within its bound; should implement 'org.reactivestreams.Subscriber
My guess is, that Flowable does not implement Disposable and thats why it wont compile. I have no clue if thats true or not, just my best guess so far.
Or do I have to change subscribeWith() to subscribe()? I dont know what the impact of that change would be.
Anyway suggestions how to make this work and get this Flowable into my compositedisposable is really appreciated.
Thanks guys!
Edit:
Tried to change the DisposableObserver into a Subscriber. But this results in the following Error:
Compiler Error
Flowables use Subscription instead of Disposable for the reason of Backpressure. Basically use Subscription.request() method to tell observable how many items I want for that moment.
Change your code:
private void uploadPicToFireBaseStorage(String path)
{
compositeDisposable.add(storageService.uploadPictureRx(path)
.subscribeOn(Schedulers.io())
.observeOn(mainScheduler)
.subscribeWith(new DisposableObserver<String>()
{
#Override
public void onNext(String s)
{
String ss = s;
System.out.println(ss);
}
#Override
public void onError(Throwable e)
{
e.printStackTrace();
}
#Override
public void onComplete()
{
view.displayToast("Picture Upload completed");
}
})
);
}
into
private void uploadPicToFireBaseStorage(String path)
{
compositeDisposable.add(storageService.uploadPictureRx(path)
.subscribeOn(Schedulers.io())
.observeOn(mainScheduler)
.subscribeWith(new ResourceSubscriber<String>()
{
#Override
public void onNext(String s)
{
String ss = s;
System.out.println(ss);
}
#Override
public void onError(Throwable e)
{
e.printStackTrace();
}
#Override
public void onComplete()
{
view.displayToast("Picture Upload completed");
}
})
);
}
Flowable works according to pub-sub pattern, i.e publisher - subscriber pattern
whereas ,
Observable works according to observer pattern
In pub-sub pattern there is middle event channel which holds the data released by the publisher and then the event channel emits the data and the subscriber gets the data in onNext(...).
Whereas,
In observer pattern the observable directly emits the data or throws the data directly to the observer. This might create back-pressure.(cause it emits the whole data in one go.)
So use (Flowable)
.subscribeWith(new ResourceSubscriber<>) // in case of flowable
Or,
.subscribeWith(new DisposableSubscriber<>)
whereas in case of (observable) use
.subscribeWith(new ResourceObserver<>)
Related
I am building an app that should connect at least 4 devices using nearby connection api. I am able to connect them intermittently, other wise only two devices are getting connected.
I am using P2P-CLUSTER Topology and sending data as file payloads which are successfully sent.
I have two questions:
Any suggestion on how to have a stable connection among 2+ devices.
While sending data as file payloads, a folder is created in the download folder. Is there any way to discard this step and be able to send data directly as file payloads without having to save them locally.
Here is my code regarding the connection part only.
private final EndpointDiscoveryCallback endpointDiscoveryCallback =
new EndpointDiscoveryCallback() {
#Override
public void onEndpointFound(String endpointId, final DiscoveredEndpointInfo info) {
arrlist.add(endpointId);
for (int i = 0; i< arrlist.size(); i++) {
connectionsClient
.requestConnection(Build.MODEL, arrlist.get(i), connectionLifecycleCallback)
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
}
#Override
public void onEndpointLost(#NonNull String endpointId) {
}
};
private final ConnectionLifecycleCallback connectionLifecycleCallback =
new ConnectionLifecycleCallback() {
#Override
public void onConnectionInitiated(#NonNull String endpointId, #NonNull ConnectionInfo connectionInfo) {
// Automatically accept the connection on both sides.
connectionsClient.acceptConnection(endpointId, new PayloadCallback() {
#Override
public void onPayloadReceived(#NonNull String s, #NonNull Payload payload) {
}
}
#Override
public void onPayloadTransferUpdate(#NonNull String s, #NonNull PayloadTransferUpdate payloadTransferUpdate) {
}
#Override
public void onConnectionResult(#NonNull String endpointId, ConnectionResolution result) {
switch (result.getStatus().getStatusCode()) {
case ConnectionsStatusCodes.STATUS_OK:
if(arrlist != null && arrlist.contains(endpointId)){
System.out.println(TAG+ " End Point Found");
} else {
arrlist.add(endpointId);
}
connectionsClient.stopDiscovery();
connectionsClient.stopAdvertising();
break;
case ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED:
// Some code
break;
case ConnectionsStatusCodes.STATUS_ERROR:
// Some code
break;
default:
}
}
#Override
public void onDisconnected(#NonNull String endpointId) {
// some code
}
};
private void startAdvertising() {
AdvertisingOptions advertisingOptions =
new AdvertisingOptions.Builder().setStrategy(STRATEGY).build();
Nearby.getConnectionsClient(context)
.startAdvertising(
android.os.Build.MODEL, getPackageName(), connectionLifecycleCallback, advertisingOptions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
private void startDiscovery() {
DiscoveryOptions discoveryOptions =
new DiscoveryOptions.Builder().setStrategy(STRATEGY).build();
Nearby.getConnectionsClient(context)
.startDiscovery(getPackageName(), endpointDiscoveryCallback, discoveryOptions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
Currently, I am fetching data from Web API using RxAndroid and Retrofit, and want to store that Data in Room database but getting an exception
As I search and found that, room database operations won't work on UI thread so I added .subscribeOn(Schedulers.io()) in RXAndroid
still it is throwing
java.lang.IllegalStateException: Cannot access the database on the main thread since it may potentially lock the UI for a long period of time.
public void onClickLogin(View view) {
io.reactivex.Observable
.zip(getLogin(Constants.EMAILID, Constants.PASSWORD),
getUserInfo(Constants.EMAILID, Constants.PASSWORD),
getProductDetails(Constants.EMAILID, Constants.PASSWORD).subscribeOn(Schedulers.io()),
.observeOn(AndroidSchedulers.mainThread())
new Function3<List<LoginModule>,
List<UserInfoModule>, ProductModule, AllZipData>() {
#Override
public AllZipData apply(List<LoginModule> loginModuleList, List<UserInfoModule> useerInfoModules, ProductModule productModule) throws Exception {
AllZipData allZipData = new AllZipData();
allZipData.setLoginModuleList(loginModuleList);
allZipData.setUserInfoModuleList(UserInfoModule);
allZipData.setProductModule(productModule);
return allZipData;
}
}).subscribe(new Observer<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
MyDatabase MyDatabase = MyDatabase.getInstance(context);
for (int i = 0; i < allZipData.getUserInfoModuleList().size(); i++) {
UserInfoTable userInfoTable = new UserInfoTable();
userInfoTable.setValue1(allZipData.getUserInfoModuleList().get(i).getValue1());
userDatabase.userDao().insertUserInfo(userInfoTable);
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: all zip data " + e.toString());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete: all data zipped");
}
});
}
how to solve this exception using RxAndroid.
How to add retryWhen();?
Where does this exception happen? If it is in onNext, that's because you specified observeOn(mainThread()) thus the database access happens on the main thread.
Try this
Observable.zip(
getLogin(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()), // <--------------------------------
getUserInfo(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()), // <--------------------------------
getProductDetails(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()) // <--------------------------------
)
.observeOn(Schedulers.io()) // <--------------------------------
.doOnNext(allZipData -> {
MyDatabase MyDatabase = MyDatabase.getInstance(context);
for (int i = 0; i < allZipData.getUserInfoModuleList().size(); i++) {
UserInfoTable userInfoTable = new UserInfoTable();
userInfoTable.setValue1(
allZipData.getUserInfoModuleList().get(i).getValue1()
);
userDatabase.userDao().insertUserInfo(userInfoTable);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
// notify UI here?
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: all zip data " + e.toString());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete: all data zipped");
}
});
I believe this line still needs some RxJava operations :
userDatabase.userDao().insertUserInfo(userInfoTable);
I believe the insertUserInfo in your dao should return a Completable.
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Clearly says that you're running on MainThread of your application, which leads to freezing of the screen. You should handle your queries or long running operations on background thread of your application.
Change this
observeOn(AndroidSchedulers.mainThread())
to
observeOn(Schedulers.io())
Use Map to perform operation. Check this
.subscribeOn(Schedulers.io())
.map {
}
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
}
.subscribeWith(new DisposableObserver<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
}
#Override
public void onError(Throwable throwable) {
}
})
What is the correct way to implement the tests below using rxjava2?
Given a list of ntp servers, test each one until you succeed.
Example:
time.nist.gov -> timeout
pool.ntp.org -> timeout
time.google.com -> success, get date
time.apple.com -> ignore
I do not want to test all in parallels but one by one. And if all fail, it restarts the test again.
Using only one server, the code I'm using is this:
public void getTime() {
timeObservable = Observable
.fromCallable(new Callable<Date>() {
#Override
public Date call() throws IOException {
return connectAndGetTime(HOST);
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable error) {
Timber.tag(TAG).e(error);
}
})
.retry(5);
timeObservable.subscribe(new Consumer<Date>() {
#Override
public void accept(Date date) {
mDate = date;
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
Timber.tag(TAG).e(throwable);
}
});
}
Thanks!
Thanks Alexei, you're right.
Why complicate things?
The end result looks like this:
public void getTime() {
timeObservable = Observable
.fromCallable(new Callable<Date>() {
#Override
public Date call() {
for (String host : Arrays.asList("time.google.com", "time.apple.com", "time.nist.gov")) {
try {
return connectAndGetTime(host);
} catch (Exception e) {
Timber.tag(TAG).d("Sync (%s) fail!", host);
}
}
return null;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable error) {
Timber.tag(TAG).e(error);
}
})
.retry(5);
timeObservable.subscribe(new Consumer<Date>() {
#Override
public void accept(Date date) {
mDate = date;
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
Timber.tag(TAG).e(throwable);
}
});
}
this function return message it return null i need solution to return value thanks
public String saveData(final User user) {
final String[] message = new String[1];
firebaseAuth = FirebaseAuth.getInstance();
firebaseAuth.createUserWithEmailAndPassword(user.getEmail().toString(), user.getPassword().toString())
.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(AuthResult authResult) {
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
message[0] = "Fail" + e.getMessage();
}
});
return message[0];
}
The problem is that firebaseAuth.createUserWithEmailAndPassword() is asynchronous operation - so onSuccess() and onFailure() callbacks will be triggered lately in the future. But you will return message[0] immediately after starting operation - that's why you get null result.
To solve that problem you can pass callbacks as arguments to your function and handle result when that callbacks will be triggered. For example,
public void saveData(final User user, OnSuccessListener<AuthResult> success, OnFailureListener failure) {
final String[] message = new String[1];
firebaseAuth = FirebaseAuth.getInstance();
firebaseAuth.createUserWithEmailAndPassword(user.getEmail().toString(), user.getPassword().toString())
.addOnSuccessListener(success).addOnFailureListener(failure);
}
and invoke it
saveData(user, new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(AuthResult authResult) {
// TODO handle result
}
}, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
String message = "Fail" + e.getMessage();
}
});
Something like that.
I'm using rx libraries im my app to call some REST api on my server and to show the results on screen.
I'm also following the MVP design pattern. So I have a Presenter and an Interactor classes.
In MainInteractor.java I have the following method:
public Observable<Card> fetchCard(final String clientId, final CardFetchedListener listener) {
Log.i(TAG, "FetchCard method");
// Manipulate the observer
return CARDS
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "CARDS Completed");
}
})
.flatMap(new Func1<Card, Observable<Card>>() {
#Override
public Observable<Card> call(final Card card) {
return ResourceClient.getInstance(card)
.getIDCard()
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.w(TAG, "interactor -> fetchCard 2", throwable);
}
}
})
.flatMap(new Func1<CardMeta, Observable<Card>>() {
#Override
public Observable<Card> call(CardMeta cardMeta) {
card.setCardMeta(cardMeta);
saveOrUpdateCardToTheDb(card);
return Observable.just(card);
}
})
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "Completed body");
}
});
}
});
}
In the logs I can see the "Completed Body" string.
The above method is being called by MainPresenter.java class as follows:
interactor.fetchCard(clientId, this)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Card>() {
#Override
public void onCompleted() {
Log.i(TAG, "fetchCard onCompleted");
view.hideProgressDialog();
view.updateCardsAdapter(cards);
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "Fetch Card error ", e);
onFailure(parseThrowable(e));
}
#Override
public void onNext(Card card) {
if (card != null) {
Log.i(TAG, card.getTenant() + " was fetched and will be displayed");
}
}
});
The problem is that the onCompleted method in the Presenter class is never bein called. I have tried to call onCompleted myself and it worked, but the problem is I don't know actually when the observable has finished emitting cards.
What am I doing wrong here?
UPDATE
CARDS is also an observable that contains meta info. It is initialized using
Observable.from(tenants)
.filter(...).flatMap(// I'm using create operator here and it is calling its onCompleted method successflly);