I want to wrap retrofit api call in another method where I can additionally show/hide loader, check for network etc. As my api returns observable, the way I ended up is below:
private <T> Observable<T> request(final Observable<T> apiCall, final ViewManager viewManager) {
return Observable.create(new Action1<Emitter<T>>() {
#Override
public void call(final Emitter<T> emitter) {
if (!NetworkUtils.isConnected(context)) {
emitter.onError(new ConnectException("network not connected"));
return;
}
viewManager.showLoader();
apiCall.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<T>() {
#Override
public void onCompleted() {
viewManager.hideLoader();
emitter.onCompleted();
}
#Override
public void onError(Throwable e) {
viewManager.hideLoader();
emitter.onError(e);
}
#Override
public void onNext(T response) {
emitter.onNext(response);
}
});
}
}, Emitter.BackpressureMode.BUFFER);
}
Is this a standard way of dealing with the problem? How do you wrap an observable inside another observable? Can anyone guide?
the idiomatic way with reactive extensions is to use composition, and this is one of the great powers of RX.
first let's define the desired behaviors using operators, what you want is something like this:
apiCall
.observeOn(AndroidSchedulers.mainThread())
.startWith(Observable.defer(() -> {
if (!NetworkUtils.isConnected(context)) {
return Observable.error(new ConnectException("network not connected"));
} else {
return Observable.empty();
}
}))
.doOnSubscribe(() -> viewManager.showLoader())
.doOnCompleted(() -> viewManager.hideLoader())
.doOnError(throwable -> viewManager.hideLoader());
now, for composing it to any network apiCall Observable, you can use compose() operator and encapsulate this logic into Transformer for that:
class CustomTransformer<T> implements Observable.Transformer<T, T> {
private final ViewManager viewManager;
private final Context context;
CustomTransformer(ViewManager viewManager, Context context) {
this.viewManager = viewManager;
this.context = context;
}
#Override
public Observable<T> call(Observable<T> apiCall) {
return apiCall
.observeOn(AndroidSchedulers.mainThread())
.startWith(Observable.defer(() -> {
if (!NetworkUtils.isConnected(context)) {
return Observable.error(new ConnectException("network not connected"));
} else {
return Observable.empty();
}
}))
.doOnSubscribe(() -> viewManager.showLoader())
.doOnCompleted(() -> viewManager.hideLoader())
.doOnError(throwable -> viewManager.hideLoader());
;
}
}
then you can compose it with any network Observable:
someRetrofitQuery
.compose(new CustomTransformer<>(viewManager, context))
...
.subscribe();
Related
Unfortunately, my REST Delete operation work only for one item. So what I was trying to do is,
Observable.just(items).flatMapIterable { items -> items }.flatMap {
//call REST DELETE for every item
}.flatMap {
// call REST GET
}
The problem is the GET call is being called for every item. How can I wait for finishing all the delete done and then perform the GET call?
Thanks in Advance.
In your case, you can apply toList() like this
fun doTask(items: List<String>):Observable<Boolean>{
return Observable.fromIterable(items)
.flatMap { processItem(it) }
.toList()
.toObservable()
.flatMap { finalTask() }
}
The problem can be solved with zip. In case any wants this
fun doTask(items: ArrayList<String>): Observable<Boolean> {
val list = arrayListOf<Observable<String>>()
items.forEach {
list.add(processItem(it))
}
return Observable.zip(list) {
}.flatMap {
finalTask()
}
}
fun processItem(s: String): Observable<String> {
print(s)
return Observable.just(s.toUpperCase())
}
fun finalTask(): Observable<Boolean> {
print("final")
return Observable.fromCallable { true }
}
Observable.just("one", "two", "three", "four", "five").subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
Log.d("ffff", s);//print: one, two, three, four, five
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
}
}, new Action() {
#Override
public void run() throws Exception {
Log.d("ffff", "complete");//print: complete
}
});
So I'm using a custom google search API to get an image URL for each name parameter in my dataset.
I'd like to iterate over each name in the dataset as a query parameter in an API call to Retrofit2.
public interface GoogleImageAPI {
#GET("v1?key=MyAPIKEY&cx=MyCustomSearchID&searchType=image&fileType=jpg&imgSize=large&alt=json")
Observable<Photos> loadPhotos(#Query("q") String artistQuery);
}
So far I've built the Retrofit adapter and API instance to return an Observable:
public Observable<Photos> getPhotosObservable(String artistName){
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://www.googleapis.com/customsearch/")
.build();
GoogleImageAPI googleImageAPI = retrofit.create(GoogleImageAPI.class);
return googleImageAPI.loadPhotos(artistName.replace(" ","+"));
}
I have a getPhotos(String name) method that creates an observable and subscribes to it. I am able to run this method with a test parameter, i.e.getPhotos("rocket") and it returns a Photos object which contains a List of the top 10 google photo search results for "rocket"
public void getPhotos(String artistName){
Observable<Photos> photosObservable = mDataManager.getPhotosObservable(artistName);
Subscription subscription = photosObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Photos>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
if(e instanceof HttpException){
HttpException response = (HttpException)e;
Log.i(TAG,"bad response: "+response.code());
}
}
#Override
public void onNext(Photos photos) {
Photos mPhotos = photos;
Log.i(TAG,"size: "+mPhotos);
}
});
}
Is there a safe/correct way of iterating over this method, consequently calling the API 100's of times without running out of memory or creating 100's of observables?
For example, could I do something like this? It seems very resource intensive:
for(String name : artistNames){
googleImageAPI.loadPhotos(name.replace(" ","+"))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Github>() {
#Override
public final void onCompleted() {
// do nothing
}
#Override
public final void onError(Throwable e) {
Log.e("GithubDemo", e.getMessage());
}
#Override
public final void onNext(Github response) {
mCardAdapter.addData(response);
}
});
}
Edit (Working example) resulting in a combined list of Photo objects:
public void getPhotos(){
mDataManager.getArtists()
.subscribeOn(Schedulers.io())
.subscribe(new Action1<JSONArray>() {
#Override
public void call(JSONArray jsonArray) {
LinkedHashMap<Integer,Artist> artistMap = presentArtists(jsonArray);
Collection<Artist> artists = artistMap.values();
Observable.from(artists)
.map(new Func1<Artist, String>() {
#Override
public String call(Artist artist) {
String artistName;
if(artist.getName().contains("(")){
artistName = artist.getName().substring(0,artist.getName().indexOf("(")-2).replace(" ","+");
}else {
artistName = artist.getName().replace(" ", "+");
}
return artistName;
}
})
.flatMap(new Func1<String, Observable<Photos>>() {
#Override
public Observable<Photos> call(String artistName) {
return mDataManager.getPhotosObservable(artistName);
}
})
.toList()
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<List<Photos>>() {
#Override
public void onCompleted() {}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Photos> objects) {
mDataManager.savePhotos(objects);
}
});
}
});
}
You can do something like this:
Observable.from(artistNames)
.map(name -> name.replace(" ", "+"))
.flatMap(name -> googleImageAPI.loadPhotos(name))
.toList()
.subscribe(new Subscriber<List<Github>>() {
#Override
public final void onCompleted() {
// do nothing
}
#Override
public final void onError(Throwable e) {
Log.e("GithubDemo", e.getMessage());
}
#Override
public final void onNext(List<Github> response) {
mCardAdapter.addAll(response);
}
})
Create an Observable from the List. Use the map operator to replace spaces with pluses. Use flatMap to call the API. Use toList to create a list of all the responses. Then you can add ALL the responses with addAll to the adapter.
I am trying my hand out and RxAndroid. I have my sync adapter querying Service A to get a List Pages(size n). For each item in Pages I have to make a request which generates another Lines(size m). That is for each item in Pages there will be m Lines.I want to combine all the n Lines into 1 list of size m*n and persist into the db .
Observable.create(new Observable.OnSubscribe<Page>(){
#Override
public void call(Subscriber<? super Page> subscriber) {
ArrayList<Page> pages = Utility.getPagesFromServer();
for (Page page : pages) {
subscriber.onNext(page);
}
subscriber.onCompleted();
}
}).map(new Func1<Page, JSONResponse>() {
#Override
public JSONResponse call(Page page) {
return Utility.getJSONObjectContainingLines(page);
}
}).map(new Func1<JSONResponse, ArrayList<Line>>() {
#Override
public ArrayList<Line> call(JSONResponse jsonResponse) {
return getLines(jsonResponse.getJSONObject());
}
})
I get pages from the server, then map and fetch the lines for each page from the server and then I parse the JSON and get the arrayList of lines. I am unsure how to proceed from here . As to now I want to iterate on each Line and not on each ArrayList .
After last map( ) use .flatMapIterable( ) and you will transform Observable<ArrayList<Line>> to Observable<Line> and in onNext(Line l) iterate its(or use forEach( )):
Observable.defer(new Func0<Observable<Page>>() {
#Override
public Observable<Page> call() {
return Observable.from(Utility.getPagesFromServer());
}
})
.map(new Func1<Page, JSONResponse>() {
#Override
public JSONResponse call(Page page) {
return Utility.getJSONObjectContainingLines(page);
}
})
.map(new Func1<JSONResponse, ArrayList<Line>>() {
#Override
public ArrayList<Line> call(JSONResponse jsonResponse) {
return getLines(jsonResponse.getJSONObject());
}
})
.flatMapIterable(new Func1<ArrayList<Line>, Iterable<Line>>() {
#Override
public Iterable<Line> call(ArrayList<Line> lines) {
return lines;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Line>() {
#Override
public void call(Line line) {
//Do something with your line
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
}, new Action0() {
#Override
public void call() {
//on complete
}
});
Or using Lambdas:
Observable.defer(() -> Observable.from(Utility.getPagesFromServer()))
.map(page -> Utility.getJSONObjectContainingLines(page))
.map(jsonResponse -> getLines(jsonResponse.getJSONObject()))
.flatMapIterable(lines -> lines)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(line -> {
//Do something with your line
}, throwable -> throwable.printStackTrace(), () -> {
//on complete
});
UPDATE:
I decided to add some some links:
Transforming-Observables
flatmap
As you can see to transform Observable<ArrayList<Line>> to Observable<Line> you should use operator flatMapIterable()
I'm trying to do a parallel download of a list of images, combining them to a map.
At first I tried to make an Observable like this:
Observable<Map<Integer, Bitmap>> getImages(final List<Activity> activities) {
return Observable.create(new Observable.OnSubscribe<Map<Integer, Bitmap>>() {
#Override
public void call(Subscriber<? super Map<Integer, Bitmap>> subscriber) {
try {
Map<Integer, Bitmap> result = new HashMap<Integer, Bitmap>();
for (Activity act : activities) {
result.put(act.getId(), downloadImage(act.getImage()));
}
subscriber.onNext(result);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
This works, but it's not what I want. Because the images are being downloaded sequentially. And I think for-loops are not nice in rxjava. So I created these Observables:
Observable<Bitmap> getImage(final Activity activity) {
return Observable.create(new Observable.OnSubscribe<Bitmap>() {
#Override
public void call(Subscriber<? super Bitmap> subscriber) {
try {
subscriber.onNext(downloadImage(activity.getImage()));
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
Observable<Map<Integer, Bitmap>> getImages(final List<Activity> activities) {
return Observable
.from(activities)
.flatMap(new Func1<Activity, Observable<Bitmap>>() {
#Override
public Observable<Bitmap> call(Activity activity) {
return getImage(activity);
}
})
.toMap(new Func1<Bitmap, Integer>() {
#Override
public Integer call(Bitmap bitmap) {
return 1; // How am I supposed to get the activity.getId()?
}
});
}
So I made an Observable for getting a single image, trying to combine them in the second one using flatMap. This works, but there are still 2 problems:
When I do a toMap(), how can I retrieve the right id to use as key for the map? I want to use the Activity object for that.
Unfortunately, the downloads are still beging processed sequentially and not in parallel. How can I fix this?
Create a wrapper class that hold a bitmap and an Activity. Say ActivityBitmap. Replace getImage with getActivityBitmap:
Observable<ActivityBitmap> getActivityBitmap(final Activity activity) {
return Observable.create(new Observable.OnSubscribe<ActivityBitmap>() {
#Override
public void call(Subscriber<? super ActivityBitmap> subscriber) {
try {
subscriber.onNext(new ActivityBitmap(activity, downloadImage(activity.getImage())));
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
and call it like below. Note that to get asynchronous downloads you use subscribeOn in the flatMap. To build the Map<Integer,Bitmap> at the end you use a different overload of toMap that allows you to specify key and value.
Observable<Map<Integer, Bitmap>> getImages(final List<Activity> activities) {
return Observable
.from(activities)
.flatMap(new Func1<Activity, Observable<ActivityBitmap>>() {
#Override
public Observable<ActivityBitmap> call(Activity activity) {
return getActivityBitmap(activity).subscribeOn(Schedulers.io());
}
})
.toMap(new Func1<ActivityBitmap, Integer>() {
#Override
public Integer call(ActivityBitmap activityBitmap) {
return activityBitmap.getActivity().getId();
}
},new Func1<ActivityBitmap, Bitmap>() {
#Override
public Integer call(ActivityBitmap activityBitmap) {
return activityBitmap.getBitmap();
}
});
}
I have a possible solution. It uses the reduce operator to convert to the map. Though, I'm not sure that subscribing to an Observable inside an Observable is good practice.
Observable<Bitmap> getImage(final Activity activity) {
return Observable.create(new Observable.OnSubscribe<Bitmap>() {
#Override
public void call(Subscriber<? super Bitmap> subscriber) {
try {
subscriber.onNext(downloadImage(activity.getImage()));
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
Observable<HashMap<Integer, Bitmap>> getImages(final List<Activity> activities) {
return Observable
.from(activities)
.reduce(new HashMap<Integer, Bitmap>(), new Func2<HashMap<Integer, Bitmap>, Activity, HashMap<Integer, Bitmap>>() {
#Override
public HashMap<Integer, Bitmap> call(final HashMap<Integer, Bitmap> bitmaps, final Activity activity) {
getImage(activity)
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.subscribe(new Action1<Bitmap>() {
#Override
public void call(Bitmap bitmap) {
bitmaps.put(activity.getId(), bitmap);
}
});
return bitmaps;
}
});
}
Would really appreciate feedback on this solution.
I would go like that:
Observable<Map<Integer, Bitmap>> getImages(List<Activity> activities) {
return Observable.from(activities)
.map(activity -> new Pair(activity.getId(), downloadImage(activity.getImage())))
.toMap(pair -> pair.first, pair -> pair.second);
}
(nota: I use retrolambda, hence the lambdas)
Apparently, something like that should be parallel:
Observable.from(activities)
.flatMap(activity ->
Observable.zip(Observable.just(activity.getId(),
downloadImage(activity.getImage())),
(k, v) -> new Pair(k, v)))
.toMap(pair -> pair.first, pair -> pair.second);
(Provided that downloadImage returns an asynchronous Observable)
When I call the Retrofit method GetTodoRepository.fetchTodo() from MainViewModel and call ends in a failure or any non-success result, I would like to let RxJava to both do onErrorReturn() and onError() so I can return a cached object in that case, but still notify MainViewModel that an error happend, so I can show error-related UI views. How do I archive this?
The current code shows how I intended to handle it.
MainViewModel
public class MainViewModel extends ViewModel
public LiveData<String> getTodo() {
getTodoRepository.fetchTodo().subscribe(new SingleObserver<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(String s) {
showProgressAnim.setValue(false);
todo.setValue(s);
}
#Override
public void onError(Throwable e) {
showProgressAnim.setValue(false);
errorMsg.setValue(e.getMessage());
}
});
return todo;
}
}
GetTodoRepository
public class GetTodoRepository {
public Single<String> fetchTodo() {
return retrofit.create(TodoApi.class)
.getTodo()
.doOnSuccess(s -> cacheManager.saveTodo(s))
.onErrorReturn(throwable -> cacheManager.getTodo())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
You can't have both signal types with a Single but you can turn fetchTodo() into Observable and emit the cached item and the error together:
fetchTodo()
.toObservable()
.onErrorResumeNext(error ->
Observable.just(cached)
.concatWith(Observable.error(error))
)
First approach which I mentioned in the comment is as follows
create a holder class for the result
class ToDoResult {
boolean isCached;
String todo;
Throwable error; // this will be set only in case of error
public ToDoResult(String todo, boolean isCached) {
this.isCached = isCached;
this.todo = todo;
}
public void setError(Throwable error) {
this.error = error;
}
}
Then make your fetchTodo() return Single<ToDoResult> instead of Single<String> as follows
public class GetTodoRepository {
public Single<ToDoResult> fetchTodo() {
return retrofit.create(TodoApi.class)
.getTodo()
.doOnSuccess(s -> cacheManager.saveTodo(s))
.map(todo -> new ToDoResult(todo,false))
.onErrorReturn(throwable -> {
ToDoResult toDoResult = new ToDoResult(cacheManager.getTodo(), true);
toDoResult.setError(throwable);
return toDoResult;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
And in you ViewModel
getTodoRepository.fetchTodo().subscribe(new SingleObserver<ToDoResult>() {
#Override
public void onSuccess(ToDoResult toDoResult) {
showProgressAnim.setValue(false);
if (toDoResult.error != null) {
errorMsg.setValue(toDoResult.error.getMessage());
} else {
todo.setValue(toDoResult.todo);
}
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onError(Throwable e) {
showProgressAnim.setValue(false);
errorMsg.setValue(e.getMessage());
}
});
In this approach, your onError will never get called since we are converting the error to a success signal always.
Second approach is as per #akarnokd mentioned in the previous answer to use an Observable and trigger the onNext and onError back to back.
public class GetTodoRepository {
public Observable<String> fetchTodo() {
return retrofit.create(TodoApi.class)
.getTodo()
.doOnSuccess(s -> cacheManager.saveTodo(s))
.toObservable()
.onErrorResumeNext(error ->
Observable.just(cached)
.concatWith(Observable.error(error))
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
And change your view model like this
getTodoRepository.fetchTodo().subscribe(new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String s) {
// this will be triggered with the todo item (cached in case of error)
}
#Override
public void onError(Throwable e) {
// this will be triggered followed by onNext in case of error
}
#Override
public void onComplete() {
}
});