RxJava getting multiple city weathers from Openweathermap - java

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()))

Related

How can I retry to connect with the server using RXJava and Retrofit?

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.

How to parse RxJava to receive just the data that i want?

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

Iterating over network API call using Retrofit2

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.

Generating tree sort of structure using RxAndroid

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()

How do I make RxJava call onErrorReturn() and still execute onError()

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() {
}
});

Categories