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.
Related
I'm sending a simple get method to my server and get the result using RxJava and Retrofit. The thing that I did is:
Interface
public interface Posts {
#GET("/typicode/demo/{path}")
Observable<List<Beans>> getPosts(#Path("path") String path);
}
RetrofitInstance
public static Retrofit getInstance() {
if (retrofit == null) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.client(client)
.build();
}
return retrofit;
}
MainActivity
Observer<List<Beans>> observer = new Observer<List<Beans>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<Beans> value) {
Log.d("Saket", value.toString());
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
};
Posts client = RetrofitClientInstance.getInstance().create(Posts.class);
client.getPosts("posts").observeOn(Schedulers.newThread()).subscribeOn(AndroidSchedulers.mainThread()).subscribe(observer);
I am getting correct output using HTTPLoggingInterceptor.
You can use subscribeWith like this
also change subscribe on to --> Schedulers.newThread() or Schedulers.io()
right now you are making network call on UI thread
client.getPosts("posts")
.subscribeOn(Schedulers.newThread()) // change
.observeOn(AndroidSchedulers.mainThread()) // change
.subscribeWith(new Observer<Response<Observable<List<Beans>>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Response<Observable<List<Beans>>> response) {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onComplete() {
}
});
It works when I use Single Observable and DisposableSingleObserver as an observer.
First of all, I am new on rxjava. I made a simple weather app which returns weather data of a city from openweathermap API. I combined with retrofit2 and rxjava. But I want to get multiple cities in order. Actually, I can do this by creating separated observable for example:
Observable<WeatherUpdated> observableAnkara = service.getWeatherData("Ankara",API_KEY);
observableAnkara.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<WeatherUpdated>() {
#Override
public void onSubscribe(Disposable d)
{
}
#Override
public void onNext(WeatherUpdated weatherUpdated)
{
tv.setText(weatherUpdated.getWeather().get(0).getDescription());
}
#Override
public void onError(Throwable e)
{
e.printStackTrace();
}
#Override
public void onComplete()
{
}
});
Observable<WeatherUpdated> observableIstanbul = service.getWeatherData("Istanbul",API_KEY);
observableIstanbul
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<WeatherUpdated>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(WeatherUpdated weatherUpdated)
{
tv_Istanbul.setText(weatherUpdated.getWeather().get(0).getDescription());
tv_Istanbul.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
After Ankara, I want to show Istanbul weather condition in the text. How can I do this? I searched it and there are many solutions like concat, startWith, zip but which one is the correct? I am confused about them.
Could you please help me?
You could have a city controls map:
Map<String, Control> cityControls= getCityControls();
and then
Observable
.fromArray(cities.keySet().toArray())
.flatMap(city -> service.getWeatherData(city, API_KEY).map(data -> new Pair<String, String>(city, data.getWeather().get(0).getDescription())))
.observeOn(...)
.subscribe(data -> citiesControls[data.getKey()].setText(data.getValue()))
WAnt to filter list with RxJava2. somehow got it working. but the prob is, it is returning only one item in List in Consumer callback(size is always 1)
here is the code:
Observable.fromIterable(arraylist)
.filter(new Predicate<BasicListModel>() {
#Override
public boolean test(BasicListModel model) throws Exception {
return true; //returning true for all items in filter
}
})
.toList()
.observeOn(Schedulers.computation())
.subscribe(new Consumer<List<BasicListModel>>() {
#Override
public void accept(List<BasicListModel> models) throws Exception {
Log.i("TAG"," size:"+models.size());
}
});
i am new to RxJAva1 or RxJava2.
You can filter with proper iteration as followed
Observable.fromIterable(offerBasicListModel)
.observeOn(Schedulers.computation())
.filter(new Predicate<BasicListModel>() {
#Override
public boolean test(BasicListModel model) throws Exception {
if (model.isDownloading()) //assume
return true; // if true, object will redirect to `doOnNext`
else
return false;
}
})
.doOnNext(new Consumer<BasicListModel>() {
#Override
public void accept(BasicListModel model) throws Exception {
Log.d("objects one by one ->",model.getId());
}
})
.toList()
.subscribe(new Consumer<List<BasicListModel>>() {
#Override
public void accept(List<BasicListModel> model) throws Exception {
Log.d(TAG, "filtered list size: "+model.size());
}
});
if you're supporting java 8, then
Observable.fromIterable(offerBasicListModel)
.observeOn(Schedulers.computation())
.filter(BasicListModel::isDownloading)
.doOnNext(
model ->Log.d(TAG,"filtered objects one by one ->",model.getId())
)
.toList()
.subscribe(model -> Log.d(TAG, "filtered list size: "+model.size()));
UPDATE
Custom User model
public class UserModel {
private int age;
private String name;
public UserModel(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
perform filtering user with age above 18
ArrayList<UserModel> userModelArrayList = new ArrayList<>();
UserModel user;
for (int i = 0; i < 20; i++) {
if (i % 2 == 0)
user = new UserModel(25, "user" + i);
else
user = new UserModel(15, "user" + i);
userModelArrayList.add(user);
}
io.reactivex.Observable
.fromIterable(userModelArrayList)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.computation())
.filter(userModel -> userModel.getAge() > 18)
.toList()
.subscribe(new SingleObserver<List<UserModel>>() {
#Override
public void onSubscribe(Disposable d) {
/* to do */
}
#Override
public void onSuccess(List<UserModel> userModels) {
Log.d("userModels", " after filtering: " + userModels.size());
}
#Override
public void onError(Throwable e) {
/* to do */
}
});
Here I am able to achieve 10 filtered user objects in onSuccess().
I suggest you to try this approach first using sample code if it is working then you can modify and trace where exactly you're doing wrong.
Can you try adding onCompleted on your Subscriber to notify when the list has emitted all the values?
as with Aks4125 solution i was able to filter List. but everytime i called Filter function it returned me different sized List.
modified my code with Observer callback. worked fine but had to write little extra code to make the list, as it was returning one Object at a time.
here is the code:
Observable.fromIterable(arraylist)
.filter(new Predicate<BasicListModel>() {
#Override
public boolean test(BasicListModel model) throws Exception {
if(filter){
return true;
}else{
return false;
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<BasicListModel>() {
#Override
public void onSubscribe(Disposable disposable) {
filteredBasicListModel.clear();
}
#Override
public void onNext(BasicListModel basicListModel) {
filteredBasicListModel.add(basicListModel);
}
#Override
public void onError(Throwable throwable) {
}
#Override
public void onComplete() {
Log.i("TAG","size :"+filteredBasicListModel.size());
});
this is working solution to me currently. if it can be optimized any better. am open to solution.
Learning and Improving.
Try adding .blockingGet() after your toList() call. This will remove all asynchronicity.
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()
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() {
}
});