I just want the numbers, but this is what i'm receiving at log:
[Boletim(grade=4.5), Boletim(grade=9.5)]
The Response:
public class GradeResponse {
#Inject
Retrofit retrofit;
#Inject
MainPresenter mainPresenter;
public void getGradeRx() {
MyApplication.getMainComponent().injectIntoGradeResponse(this);// informando ao dagger sobre o uso de um component e a necessidade de injetar dependência
Subscription getGrade = retrofit
.create(GradeService.class)
.getGrade()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(model -> {
return model.getBoletim();
})
.subscribe(new Observer<Boletim[]>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.i(TAG, "saporra vai me matar ainda");
}
#Override
public void onNext(Boletim[] grades) {
Log.i(TAG, Arrays.asList(grades).toString());
}
});
}
}
The models:
GradeModel:
#SerializedName("boletim")
#Expose
private Boletim[] boletim;
Boletim.class
public class Boletim {
#SerializedName("grade")
#Expose
private double grade;
The retrofit service is ok, the dependency injection is working. I'm receiving the onSuccess method from rxJava, i just need now receiving only the numbers without this "[Boletim(grade=".
You are seeing the toString of your objects because you get the entire object in your map!
only the numbers without this "[Boletim(grade=".
Way can't you map again and extract it?
.map(model -> { // This map returns a Boletim[]
return model.getBoletim();
})
.map(boletim -> { // This map returns a double[]
Double grades = new Double[boletim.length];
for (int i =0; i < grades.length ; i++) {
grades[i] = boletim[i].getGrade() ;
}
return grades;
}).subscribe(new Observer<Double[]>() { // This subscribes to a double[]
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.i(TAG, "saporra vai me matar ainda");
}
#Override
public void onNext(Double[] grades) {
Log.i(TAG, Arrays.toString(grades));
}
}
Or you could put the for loop into onNext
If you don't want an Observable of arrays, then use flatMap
Related
Code
new Retrofit.Builder().baseUrl(Constants.BASE_URL_FILES).addCallAdapterFactory(RxJava3CallAdapterFactory.create()).addConverterFactory(GsonConverterFactory.create()).build().create(RetrofitInterface.class).getCountries().observeOn(AndroidSchedulers.mainThread()).subscribe(new SingleObserver<List<CountryModel>>() {
#Override
public void onSubscribe(#io.reactivex.rxjava3.annotations.NonNull Disposable d) {
MainActivity.activityMainBinding.activityMainLinearProgressIndicatorLoading.setVisibility(View.VISIBLE);
}
#Override
public void onSuccess(#io.reactivex.rxjava3.annotations.NonNull List<CountryModel> countryModels) {
fragmentCountriesBinding.fragmentCountriesRecyclerViewCountries.setAdapter(new CountriesAdapter(requireContext(), countryModels));
MainActivity.activityMainBinding.activityMainLinearProgressIndicatorLoading.setVisibility(View.GONE);
}
#Override
public void onError(#io.reactivex.rxjava3.annotations.NonNull Throwable e) {
MainActivity.activityMainBinding.activityMainLinearProgressIndicatorLoading.setVisibility(View.GONE);
if (!MainActivity.isNetworkConnected(requireContext()))
Snackbar.make(MainActivity.activityMainBinding.getRoot(), R.string.no_internet, BaseTransientBottomBar.LENGTH_INDEFINITE).setMaxInlineActionWidth(1).setAction(R.string.retry, view ->
{
}).show();
else
Snackbar.make(MainActivity.activityMainBinding.getRoot(), R.string.something_went_wrong, BaseTransientBottomBar.LENGTH_INDEFINITE).setMaxInlineActionWidth(1).show();
}
});
RetrofitInterface
public interface RetrofitInterface {
#GET("GetCountries.php")
Single<List<CountryModel>> getCountries();
}
CountryModel
public class CountryModel {
private int countryId;
private String countryName, countryIcon;
public int getCountryId() {
return countryId;
}
public String getCountryName() {
return countryName;
}
public String getCountryIcon() {
return countryIcon;
}
}
As you can see inside the onError method, I want when the user clicks on the retry button must retry the call with the server again.
I read the articles talking about retryWhen method and tried to understand this image, but I can't understand it. And I don't know how to use it with my scenario.
My problem is like this question exactly.
My solution is simple and just a temporary/hack rather than spending couple of days using retryWhen().
In the code below, I encapsulate your request call to a function.
If there is a network error, just do a recursive call with the function until it will be successful.
private void sendRequest(){
new Retrofit.Builder().baseUrl(Constants.BASE_URL_FILES).addCallAdapterFactory(RxJava3CallAdapterFactory.create()).addConverterFactory(GsonConverterFactory.create()).build().create(RetrofitInterface.class).getCountries().observeOn(AndroidSchedulers.mainThread()).subscribe(new SingleObserver<List<CountryModel>>() {
#Override
public void onSubscribe(#io.reactivex.rxjava3.annotations.NonNull Disposable d) {
MainActivity.activityMainBinding.activityMainLinearProgressIndicatorLoading.setVisibility(View.VISIBLE);
}
#Override
public void onSuccess(#io.reactivex.rxjava3.annotations.NonNull List<CountryModel> countryModels) {
fragmentCountriesBinding.fragmentCountriesRecyclerViewCountries.setAdapter(new CountriesAdapter(requireContext(), countryModels));
MainActivity.activityMainBinding.activityMainLinearProgressIndicatorLoading.setVisibility(View.GONE);
}
#Override
public void onError(#io.reactivex.rxjava3.annotations.NonNull Throwable e) {
MainActivity.activityMainBinding.activityMainLinearProgressIndicatorLoading.setVisibility(View.GONE);
if (!MainActivity.isNetworkConnected(requireContext()))
Snackbar.make(MainActivity.activityMainBinding.getRoot(), R.string.no_internet, BaseTransientBottomBar.LENGTH_INDEFINITE).setMaxInlineActionWidth(1).setAction(R.string.retry, view ->
{
}).show();
// IF NO NETWORK AVAILABLE just do recursive call of the method
sendRequest();
else
Snackbar.make(MainActivity.activityMainBinding.getRoot(), R.string.something_went_wrong, BaseTransientBottomBar.LENGTH_INDEFINITE).setMaxInlineActionWidth(1).show();
}
});
}
I didn’t reach a solution, so I decided to use retryUntil instead of retryWhen.
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.
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.
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() {
}
});