I have 3 layers in my app. Layer1 subscribes to Observable from layer2. Layer2 subscribes to layer3 in order to emit returned data to layer1.
Layer1
layer2.getData(data).subscribe(newData -> {Log.d("onNext", "returned");},
throwable -> {Log.d("onError", throwable.getMessage());});
Suppose layer3 has a method called downloadDataFromApi(data);
public Observable<Data> getData(String data) {
return Observable.create(new Observable.OnSubscribe<Data>() {
#Override
public void call(Subscriber<? super Data> subscriber) {
Data data = new Data();
subscriber.onNext(data);
subscriber.onCompleted();
// Can't find a way to connect to layer3.
}
});
}
What do I need to do in layer2's getData() method? I basically want to have logics before returning Observable back to layer1.
Does that make sense?
Just return the Observable directly. Then layer1 handles subscription as usual.
class Layer2 {
public Observable<Data> getData(String data) {
return layer3.getData(data);
}
}
From what I see you have 3 layers (presentation, business logic, data access).
So what you could do is the following:
class PresentationLayer {
private BusinessLogicLayer layer;
PresentationLayer() {
layer = new BusinessLogicLayer();
}
public void showName() {
layer.getNameWithoutRxPrefix()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
#Override
public void accept(String name) throws Exception {
// show name somewhere
Log.d("PresentationLayer", "name: " + name);
}
});
}
}
class BusinessLogicLayer {
private DataAccessLayer layer;
BusinessLogicLayer() {
layer = new DataAccessLayer();
}
public Observable<String> getNameWithoutRxPrefix() {
return layer.getName()
.map(new Function<String, String>() {
#Override
public String apply(String name) throws Exception {
return name.replace("Rx", "");
}
});
}
}
class DataAccessLayer {
public Observable<String> getName() {
return Observable.just("RxAndroid");
}
}
As you can see, I return an Observable in my data access layer (getName), and chain another method to it in my business logic method (map) before returning it to the presentation layer.
Related
I use retrofit2 with rxjava extension.
I have a list of REST API urls and want to do this:
for each
check whether a corresponding file locally exists
if yes: call the API and store the response or the HTTP error
if not: store a customized error
return the list of those results
My problem is: apply returns (with an empty RequestResult) before the server response is received. I think, I understand why, but I don't know how to fix it, because I need to return a RequestResult and not the Retrofit observable.
How can this be solved?
Here is my code:
#GET
Observable<Response<ResponseBody>> enroll(#Url String url);
class RequestResult {
CustomException error;
Response<ResponseBody> response;
}
Observable<ClassOfListItem> observable = Observable.fromIterable(listOfItems);
observable
.flatMap(new Function<ClassOfListItem, ObservableSource<RequestResult>>() {
#Override
public ObservableSource<RequestResult> apply(ClassOfListItem listItem) throws Exception {
RequestResult requestResult = new RequestResult();
if (fileExists(listItem.url)) {
Observable<Response<ResponseBody>> callObservable = restAPI.enroll(listItem.url)
.subscribeOn(Schedulers.io());
callObservable
.subscribe(new DisposableObserver<Response<ResponseBody>>() {
#Override
public void onNext(Response<ResponseBody> responseBodyResponse) {
onPremiseEnrollmentResult.response = responseBodyResponse;
}
#Override
public void onError(Throwable e) {
onPremiseEnrollmentResult.error = new CustomException(e);
}
#Override
public void onComplete() {
}
});
}
else {
requestResult.error = new CustomException("file not found");
}
return Observable.just(requestResult);
}
}
.toList()
.observerOn(AndroidScheduler.mainThread())
.subscribe(new DisposableSingleObserver<List<RequestResult>>() {
#Override
public void onError(Throwable e) {
Log.d("onError", e.getMessage());
}
#Override
public void onSuccess(List<RequestResult> requestResults) {
// parse results
}
}
)
The flatMap() operator allows you to turn one observable into a different observable. You have a nested observer chain inside your apply() which is not part of the observer chain, so it will be empty because it has not completed yet.
To fix this, when the file exists, return the observable.
observable
.flatMap(new Function<ClassOfListItem, ObservableSource<RequestResult>>() {
#Override
public ObservableSource<RequestResult> apply(ClassOfListItem listItem) throws Exception {
RequestResult requestResult = new RequestResult();
if (fileExists(listItem.url)) {
return restAPI.enroll(listItem.url)
.subscribeOn(Schedulers.io());
}
return Observable.error( new CustomException("file not found") );
}
}
.toList()
.observerOn(AndroidScheduler.mainThread())
.subscribe(new DisposableSingleObserver<List<RequestResult>>() {
#Override
public void onError(Throwable e) {
Log.d("onError", e.getMessage());
}
#Override
public void onSuccess(List<RequestResult> requestResults) {
// parse results
}
}
If you need to capture both errors and successes into the list, then you can add map() operator to wrap RequestResult around the response and onErrorResumeNext() to wrap RequestResult around the error before the toList() operator.
If you are making api call on background thread then what you can do is invoke it synchronously....in your case your retrofit api method would change to following
Call<Response<ResponseBody>> enroll(#Url String url);
and you'd invoke by calling restAPI.enroll(listItem.url).execute()
In Android, I want to use the call AdvertisingIdClient.getAdvertisingIdInfo(getContext()).getId() on a separate thread (IO-thread) and handle the string on the main thread.
I wan't to do this with RxJava2.
This is what I have now: (which works)
SingleOnSubscribe<String> source = new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
e.onSuccess(AdvertisingIdClient.getAdvertisingIdInfo(getContext()).getId());
}
};
Single.create(source)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Timber.e(throwable.getMessage());
}
})
.subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
advertisingId = s;
}
});
What I would prefer, which is purely taste, is if I could "just" create the stream and handle it all in the flow of methods. Such as: (warning, super pseudo code)
Completable
.empty()
.produce(() -> String {
return makeString();
})
.sub/obs-On()...
.subscribe(coolString -> {mVariable = coolString})
So, Make an Observable and turn it into an Observable by executing some function.
Just use defer or fromCallable like in this example:
Observable<String> stringObservable = Observable.fromCallable(() -> {
return getStuff();
});
Test
#Test
public void fromCallable() throws Exception {
Observable<String> stringObservable = Observable.fromCallable(() -> {
return getStuff();
});
ExecutorService executorService = Executors.newSingleThreadExecutor(runnable -> {
return new Thread(runnable, "myFancyThread");
});
Scheduler scheduler = Schedulers.from(executorService);
TestObserver<String> test = stringObservable.subscribeOn(scheduler)
.test();
test.await()
.assertResult("wurst");
assertThat(test.lastThread().getName()).contains("myFancyThread");
}
private String getStuff() {
return "wurst";
}
I have the following methods
Document createDocument(String url);
List<MediaContent> getVideo(Document doc);
List<MediaContent> getImages(Document doc);
List< MediaContent> will be consumed by
void appendToRv(List<MediaContent> media);
I like to use RxJava2 such that
CreateDocument -> getVideo ->
-> appendToRv
-> getImages ->
(also, the video output should be ordered before images).
How would I go about doing that? I tried flatMap, but it seems to only allow a single method to be used
Single<List<MediaContent>> single =
Single.fromCallable(() -> createDocument(url))
// . ?? ..
// this is the part i am lost with
// how do i feed document to -> getVideo() and getImage()
// and then merge them back into the subscriber
//
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
single.subscribe(parseImageSubscription);
The DisposableSingleObserver
parseImageSubscription = new DisposableSingleObserver<List<MediaContent>>() {
#Override
public void onSuccess(List<MediaContent> media) {
if(media!=null) {
appendToRv(media);
}
}
#Override
public void onError(Throwable error) {
doSnackBar("error loading: '" + q + "'");
}
};
the single observables for getVideos and getImages
Single<List<MediaContent>> SingleGetImage(Document document ) {
return Single.create(e -> {
List<MediaContent> result = getImage(document);
if (result != null) {
e.onSuccess(result);
}else {
e.onError(new Exception("No images found"));
}
});
}
Single<List<MediaContent>> singleGetVideo(Document document ) {
return Single.create(e -> {
List<MediaContent> result = getVideo( document);
if (result != null) {
e.onSuccess(result);
}else {
e.onError(new Exception("No videos found"));
}
});
}
assuming you want to execute in parallel the getVideos and getImages requests, you can use flatMap() with zip(), zip will collect the 2 emissions from both Singles, and you can combine the 2 results to a new value, meaning you can sort the videos MediaContent list , and combine it with the images MediaContent list, and return unified list (or whatever other object you'd like):
Single<List<MediaContent>> single =
Single.fromCallable(() -> createDocument(url))
.flatMap(document -> Single.zip(singleGetVideo(document), SingleGetImage(document),
(videoMediaContents, imageMediaContents) -> //here you'll have the 2 results
//you can sort combine etc. and return unified object
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
single.subscribe(parseImageSubscription)
Observable.zip() could implement it perfect. The Observer will receive a merged result by this method.
public void zip() {
Observable<Integer> observable1 = Observable.just(1);
Observable<Integer> observable2 = Observable.just(2);
Observable.zip(observable1, observable2, new Func2<Integer, Integer, Integer>() {
#Override
public Integer call(Integer integer, Integer integer2) {
return integer + integer2;
}
}).subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Integer o) {
Logger.i(o.toString());
//Here will print 3.
}
});
}
I try to replicate database trigger function with Realm with Rx. Once I get RealmList emitted, I do some stuff with it and save. Sadly, this results into Realm's change listener to be executed again, emitting the list over and over again.
Dummy example:
realm.where(MyRealmObject.class)
.equalTo("state", "new")
.findAll()
.asObservable()
.flatMap(new Func1<RealmResults<MyRealmObject>, Observable<MyRealmObject>>() {
#Override
public Observable<MyRealmObject> call(RealmResults<MyRealmObject> list) {
return Observable.from(list);
}
})
.subscribe(new Action1<MyRealmObject>() {
#Override
public void call(final MyRealmObject object) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
// do any realm change
}
});
}
});
Once I commit the transaction in subscriber, new RealmList is emited from observable. I know why this happens, I just don't see any way how to workaround this.
This takes us to my question. Is there any way how to replicate trigger functionality with realm where I will do any realm change?
Workaround can be built with helper stream determing whether next item from db should be consumed. Every data store into db should be accompanied with write into helper stream. Running test below yields:
upstream: IgnoreAction{action='start', ignoreNext=false}
result: 1
result: 2
result: 3
upstream: IgnoreAction{action='1', ignoreNext=true}
upstream: IgnoreAction{action='2', ignoreNext=true}
upstream: IgnoreAction{action='3', ignoreNext=true}
So, first data ("start") is consumed, and writes triggered in onNext are ignored.
#Test
public void rxIgnore() throws Exception {
MockDb mockDb = new MockDb();
BehaviorSubject<Boolean> ignoreNextStream = BehaviorSubject.create(false);
Observable<String> dataStream = mockDb.dataSource();
dataStream.zipWith(ignoreNextStream, Data::new)
.doOnNext(action -> System.out.println("upstream: " + action))
.filter(Data::isTakeNext)
.flatMap(__ -> Observable.just(1, 2, 3))
.subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Integer val) {
System.out.println("result: " + val);
ignoreNextStream.onNext(true);
mockDb.data(String.valueOf(val));
}
});
mockDb.data("start");
Observable.empty().delay(1, TimeUnit.MINUTES).toBlocking().subscribe();
}
private static class Data {
private final String action;
private final boolean ignoreNext;
public Data(String action, boolean ignoreNext) {
this.action = action;
this.ignoreNext = ignoreNext;
}
public boolean isTakeNext() {
return !ignoreNext;
}
#Override
public String toString() {
return "IgnoreAction{" +
"action='" + action + '\'' +
", ignoreNext=" + ignoreNext +
'}';
}
}
private static class MockDb {
private final Subject<String, String> subj = PublishSubject.<String>create()
.toSerialized();
public void data(String action) {
subj.onNext(action);
}
Observable<String> dataSource() {
return subj;
}
}
I'm very new to RXJava.
I have a function called politelyrefresh() that concats two observables together, but the functions in these two observables only run the first time I called politeRefresh, I'm not sure this is the right way to do it. What I want is run this function inside the observables everytime.
public void politelyRefresh() {
Observable.concat(refreshStoreDataObservable, refreshProjectDataObservable)
.finallyDo(()-> {
try {
//someother other long runnning-network requests
} catch (Exception e) {
Log.e(TAG, "politelyRefresh finallyDo Error", e);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(reloadUiFromLocalStorageSubscriber);
}
//the other observable is pretty much the same but making another request
Observable<String> refreshStoreDataObservable = Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
//DOESN'T GET HERE SECOND TIME!
Store.syncStores(new ListCallback() {
#Override
public void syncSuccess() {
getSyncStateManager().setStoresRefreshed();
subscriber.onCompleted();
}
#Override
public void syncError() {
subscriber.onError(new Throwable("SYNC STORES ERROR"));
getSyncStateManager().setStoresSyncCompleted();
}
});
}
});
Subscriber<String> reloadUiFromLocalStorageSubscriber = new Subscriber<String>() {
#Override
public void onCompleted() {
if (mStoreRefreshLayout != null){
mStoreRefreshLayout.setRefreshing(false);
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "reloadUiFromLocalStorageSubscriber: onError", e);
if (mStoreRefreshLayout != null){
mStoreRefreshLayout.setRefreshing(false);
}
}
#Override
public void onNext(String s) {
Log.d(TAG, "reloadUiFromLocalStorageSubscriber: onNext " + s);
}
};
I think you're looking for Observable.defer(). What this basically does is defer the creation of the Observable to when it is being subscribed to.
Here's a quick example:
public class Refresher {
Refresher() {
politelyRefresh();
politelyRefresh();
}
public void politelyRefresh() {
Observable.defer(() -> Observable.concat(refreshProjectData(), refreshStoreData()))
.map(this::processData)
.subscribe(this::printData);
}
private Observable<String> refreshStoreData() {
System.out.println("StoreData Refreshed");
return Observable.just("data1","data2","data3");
}
private Observable<String> refreshProjectData() {
System.out.println("ProjectData Refreshed");
return Observable.just("Project1","Project2", "Project3");
}
private String processData(String data) {
return data + " processed";
}
private void printData(String data) {
System.out.println(data);
}
}
If you instantiate our refresher object, you'll get
StoreData Refreshed
StoreData Refreshed
Project1 processed
Project2 processed
Project3 processed
data1 processed
data2 processed
data3 processed
StoreData Refreshed
StoreData Refreshed
Project1 processed
Project2 processed
Project3 processed
data1 processed
data2 processed
data3 processed
If you'd like something to run on a different thread, you'd specify that on the specific observable you're looking to run on a non-ui thread.
So, for example, you might want to run the Observable in politelyRefresh on a background thread and subscribe to it on the UI thread. The creation of the other Observables will happen in a background thread too!
I finally got this to work by move the subscriber from an class instance to inside the .subscribe() function(). I have no idea why this is happening.
Observable.concat(refreshStoreDataObservable, refreshProjectDataObservable)
.finallyDo(()-> {
try {
//someother other long runnning-network requests
} catch (Exception e) {
Log.e(TAG, "politelyRefresh finallyDo Error", e);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( new Subscriber<String>() { /*rest of code */}); //**here