How to properly use Observable.zip() to combine multiple Retrofit call? - android

I'm new to RxJava. My requirement is to do 3 retrofit call at start and wait until all of theme executed. Here is what i had implemented and Its working perfectly but I want know, is this code could be better than this and I implemented schedulers correctly or not.
public class CombinedGroupProductPage {
private List<Product> groupProductList;
private List<Product> relatedProductList;
private List<Product> upsellProductList;
//constructor and getter setters here
.....
.....
}
Here is my implementation
private void getAllData() {
loadingProgress.setVisibility(View.VISIBLE);
ApiInterface apiService = ApiClient.getRxClient().create(ApiInterface.class);
Observable<List<Product>> call = apiService.getRelatedProduct1(gson.toJson(model.getGroupedProducts()))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io());
Observable<List<Product>> call1 = apiService.getRelatedProduct1(gson.toJson(model.getRelatedIds()))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io());
Observable<List<Product>> call2 = apiService.getRelatedProduct1(gson.toJson(model.getUpsellIds()))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io());
Observable<CombinedGroupProductPage> combined = Observable.zip(call, call1, call2, new Function3<List<Product>, List<Product>, List<Product>, CombinedGroupProductPage>() {
#Override
public CombinedGroupProductPage apply(List<Product> list, List<Product> list2, List<Product> list3) throws Exception {
return new CombinedGroupProductPage(list, list2, list3);
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread());
combined.subscribe(new Observer<CombinedGroupProductPage>() {
#Override
public void onSubscribe(Disposable d) {
// loadingProgress.setVisibility(View.VISIBLE);
}
#Override
public void onNext(CombinedGroupProductPage combinedGroupProductPage) {
Log.e("Tag", combinedGroupProductPage.toString());
Log.e("Tag", combinedGroupProductPage.getGroupProductList().get(0).getName());
loadingProgress.setVisibility(View.GONE);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
}
Please tell me this code can be reduced or not? Any help would be appreciated thanks.

Use Schedulers.io() instead of Schedulers.newThread(). Schedulers.io() uses a thread pool whereas Schedulers.newThread() doesn't. Creating threads is expensive and should be avoided if possible.
Using .subscribeOn(Schedulers.io()) for your different calls also allow you to remove the now useless .observeOn(Schedulers.io()).

Related

MVVM architecture problem when trying to get my item details?

Here I am not asking for code problem, but just architecture, I will show what Iof course.
So I was just playing around with this MVVM architecture, I was displaying a list of items, I click an item I see its details, there are many categories in those details, meaning that for example I had a personal data displayed in one cardview, informations about his credit card for example in an other cardview, etc.. For some Users who are connected, credit cards data must not be displayed, but when I created the view model in the begging it was like this :
public class ContractViewModel extends ViewModel {
public MutableLiveData<ContractModel> contract = new MutableLiveData<ContractModel>();
public MutableLiveData<Boolean> isLoading = new MutableLiveData<Boolean>();
public MutableLiveData<Boolean> error = new MutableLiveData<Boolean>();
#Inject
ContractService contractService;
CompositeDisposable disposable = new CompositeDisposable();
public ContractViewModel(){
DaggerContractApiComponent.create().inject(this);
}
public void call(SingleContractRequest singleContractRequest){
fetchContract(singleContractRequest);
}
public void fetchContract(SingleContractRequest singleContractRequest){
isLoading.setValue(true);
disposable.add(
contractService.getContract(singleContractRequest)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ContractModel>() {
#Override
public void onSuccess(ContractModel contractModel) {
isLoading.setValue(false);
error.setValue(false);
contract.setValue(contractModel);
}
#Override
public void onError(Throwable e) {
error.setValue(true);
isLoading.setValue(false);
e.printStackTrace();
}
})
);
}
#Override
protected void onCleared(){
super.onCleared();
disposable.clear();
}
}
But now I need the user connected role in the app to display the possible data that he can consult. The first thought coming to my head was just calling the service that return my userConnected.
public class ContractViewModel extends ViewModel {
public MutableLiveData<ContractModel> contract = new MutableLiveData<ContractModel>();
public MutableLiveData<UserConnected> userConnected = new MutableLiveData<UserConnected>();
public MutableLiveData<Boolean> isLoading = new MutableLiveData<Boolean>();
public MutableLiveData<Boolean> error = new MutableLiveData<Boolean>();
#Inject
ContractService contractService;
#Inject
UserConnectedService userConnectedService;
CompositeDisposable disposable = new CompositeDisposable();
public ContractViewModel(){
DaggerContractApiComponent.create().inject(this);
DaggerUserConnectedApiComponent.create().inject(this);
}
public void call(SingleContractRequest singleContractRequest){
fetchContract(singleContractRequest);
}
public void getUserConnected(){
disposable.add(
userConnectedService.getUserConnected()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<UserConnected>() {
#Override
public void onSuccess(UserConnected userConnectedParams) {
userConnected.setValue(userConnectedParams);
}
#Override
public void onError(Throwable e) {
}
})
);
}
public void fetchContract(SingleContractRequest singleContractRequest){
isLoading.setValue(true);
disposable.add(
contractService.getContract(singleContractRequest)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ContractModel>() {
#Override
public void onSuccess(ContractModel contractModel) {
isLoading.setValue(false);
error.setValue(false);
contract.setValue(contractModel);
}
#Override
public void onError(Throwable e) {
error.setValue(true);
isLoading.setValue(false);
e.printStackTrace();
}
})
);
}
#Override
protected void onCleared(){
super.onCleared();
disposable.clear();
}
}
But it is not clean, I have to call two methods at the same time in my Activity, which is terrible, the second thought is that I have to edit my contract type from MutableLiveData<ContractModel> to something like this MutableLiveData<NewModel> knowing that this NewModel architecture is like this :
public NewModel {
ContractModel contractModel;
UserModel userModel;
// ofc here a constuctor and getters and setters
}
But this has a problem too that will be caused in the backend, because what if I need the userConnected in an other activity ? I will have to write similar code in multiple places to get this userConnected, but I want to get it once.
Any help, thoughts would be really helpful.

How to return value in rxJava

I'm new into rxJava and it's making my head spin. Basically I'm pulling data from youtube api with retrofit which gives back Observable and with youtubeDataMapper I'm mappng it into Youtube Pojo object which contains String videoID. So my question is, how to make this method return that string instead of Completable?
This is my method:
#Override
public Completable downloadVideoUrl(String query) {
addSubscription(youtubeApi.getYoutubeId(query, Constants.youtubeApi)
.map(youtubeDataMapper::map)
.subscribeOn(subscribeScheduler)
.observeOn(observeScheduler)
.subscribe());
return Completable.complete();
}
You have two choices:
Make your downloadVideoUrl return Observable instead of Completable:
Preferred way:
#Override
public Completable downloadVideoUrl(String query) {
return youtubeApi.getYoutubeId(query, Constants.youtubeApi)
.map(youtubeDataMapper::map)
.subscribeOn(subscribeScheduler)
.observeOn(observeScheduler);
}
Notice lack of subscribe operator here.
Then wherever you want to get videoId:
downloadVideoUrl(query)
.subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String videoId) {
// do whatever you want with videoId
}
});
Use toBlocking().first()
This is not preffered as you block current Thread until Observable finishes
#Override
public String downloadVideoUrl(String query) {
return youtubeApi.getYoutubeId(query, Constants.youtubeApi)
.map(youtubeDataMapper::map)
.subscribeOn(subscribeScheduler)
.observeOn(observeScheduler)
.toBlocking().first();
}
First of all, it is better to make Retrofit return Single instead of Observable because you are expecting a single server response (and not a sequence of responses).
Secondly, Completable.complete() is a factory method for a Completable that does nothing at all. So you don’t need it here.
Regarding String videoID, it depends on what you are planning to do with it. Also, I have no idea what your .addSubscription() is doing.
I would suggest doing something like the following:
class YourClass {
private final CompositeSubscription compositeSubscription = new CompositeSubscription();
// you must call compositeSubscription.clear() either in class .finalize() or on some UI lifecycle event
void yourMethod() {
final Single videoID = youtubeApi.getYoutubeId(query, Constants.youtubeApi)
.map(youtubeDataMapper::map)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
final Subscription subscription = videoID.subscribe(new SingleSubscriber() { 
 #Override
 public void onSuccess(String value) {
// TODO: do whatever with the value
}
#Override
public void onError(Throwable error) {
// TODO: log and/or display error
}
});
compositeSubscription.add(subscription);
}
}

How to extract from rxJava and put it on realm offline database?

I need to download a long list of 30k airports and put it on a offline database.
I made this code to download the json from the web:
bFetch.setOnClickListener(new View.OnClickListener() {
public void onClick(View v)
{
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(GithubService.SERVICE_ENDPOINT).build();
GithubService service = retrofit.create(GithubService.class);
service.getAirport()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Airport>>() {
#Override
public void onCompleted()
{
bClear.setText("OK");
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Airport> airports)
{
Log.d("msh",String.valueOf(airports.size()));
}
});
}
});
and it works very well, but if I want to extract only one object, like map or a flatMap, it gives me this:
service.getAirport()
.map(new Func1<List<Airport>, Airport>()
{
#Override
public Airport call(List<Airport> airports) {
return null;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Airport>>() {
#Override
public void onCompleted()
{
bClear.setText("OK");
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Airport> airports)
{
Log.d("msh",String.valueOf(airports.size()));
}
});
}
});
with the error:
Cannot resolve method 'subscribe(anonymous
rx.Subscriber>)
so:
what I have to do to solve it? My problem is that I don't understand very well rX and I have also a bit confusion
could I put data in realm database in map() method (if it works)?
Thank you
Since you're mapping from a List<Airport> to an Airport, you need to have a Subscriber<Airport> instead of Subscriber<List<Airport>>, along with the same change to the onNext method.
looks like it would compile with Java8 and RxJava2-RC5. I changed subscriber param from List to X and the onNext method from List to X. Maybe you coulde provide some more intel on your environment. Please notice that returning null is not possible anymore in RxJava2.
Furthermore notice that using newThread-Scheduler is not a good idea.
This scheduler simply starts a new thread every time it is requested
via subscri beOn() or observeOn() . newThread() is hardly ever a good
choice, not only because of the latency involved when starting a
thread, but also because this thread is not reused. --Tomasz Nurkiewicz from "Reactive Programming with RxJava"
Example-Impl with RxJava2-RC5
Observable.just(Arrays.asList("1", "2", "3"))
.map(new Function<List<String>, String>() {
#Override
public String apply(List<String> s) throws Exception {
return null;
}
}).subscribeOn(Schedulers.newThread())
.subscribe(new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String value) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});

rxAndroid retrofit get result of each call from the sequence

I have to execute 3 API calls in the sequence and to do so
I use observable.concatMap(new Func1<>...)
and at the last one I have a subscriber to change activity
However I want to update progressBar in UI thread to let user know that part of task is done.
private void getAllData() {
updateUserTask(
getUserScheduleObservable(
getCurrentUserObservable()));
}
private void updateUserTask(Observable<TaskWrapper> observable) {
wrapObservable(observable)
.subscribe(new Subscriber<TaskWrapper>() {
#Override
public void onNext(TaskWrapper taskWrapper) {
openCurrentFragment();
hideProgressIndicators();
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
}
});
}
private Observable<TaskWrapper> getUserScheduleObservable(Observable<ScheduleWrapper> observable) {
return observable.concatMap(
scheduleWrappers1 -> apiManager.getRouteObservable(vehicleDeliveryAreaRiderBundle.getVehicle().getId()));
}
private Observable<ScheduleWrapper> getCurrentUserObservable() {
return apiManager.getUserObservable().concatMap(
user -> apiManager.getCurrentScheduleObservable()
);
}
I think that you are looking for something like this.
public class ExampleUnitTest {
#Test
public void testSample() throws Exception {
Observable<String> first = Observable.just("First");
Observable<String> second = Observable.just("Second");
Observable<String> third = Observable.just("Third");
Observable.concat(first, second, third)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(this::updateProgress)
.subscribe();
}
private void updateProgress(String s) {
System.out.println(String.format("Notify your progress that %s ended", s));
}
}
Just concatenating those observables, you can achieve the expected result.
Hope that it helps.
Best regards.

How to structure my app using MVP with rxjava and retrofit to get data out of Observables?

So I'll try to keep this question as to-the-point as possible, but it will involve code snippets that traverse an entire codepath.
For context, I am fairly new and completely self-taught for Android dev, so please notify me of any clear misunderstandings/poor organization throughout. The main focus of the question is bug I am experiencing now, which is that, after a network request, the variable that was supposed to be set as a result of that network request is null, because the code moved forward before the network request completed.
Here is my activity method. It is supposed to populate the mFriends variable with the result of mUserPresenter.getUserList(), which is (unfortunately) null:
/**
* Grabs a list of friends, populates list with UserAdapter
*/
#Override
public void onResume(){
super.onResume();
mUserPresenter = new UserPresenter();
mFriends = mUserPresenter.getUserList();
if (mGridView.getAdapter() == null) {
UserAdapter adapter = new UserAdapter(getActivity(), mFriends);
mGridView.setAdapter(adapter);
}
else{
((UserAdapter)mGridView.getAdapter()).refill(mFriends);
}
}
Here is how I am structuring my UserPresenter method getUserList:
public List<User> getUserList()
{
ApiService.get_friends(this);
return mUserList;
}
The real magic happens in the ApiService class:
public static void get_friends(final UserPresenter userPresenter){
ApiEndpointInterface apiService = prepareService();
apiService.get_friends().
observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Action1<List<User>>()
{
#Override
public void call(List<User> users) {
userPresenter.setList(users);
}
}
);
}
My thinking was, that by calling userPresenter.setList(users) in ApiService, that would set mUserList to the response from the api request. However, instead, mUserList == null at the time that getUserList responds.
Any ideas of how I can structure this?
I have also started to learn something similar. Here, I would rather use callbacks.
In your presenter,
public void setList(List<User> users) {
yourView.setUserList(users);
}
And your activity which implements a view (MVP)
#Override
public void setUserList(List<User> users) {
((UserAdapter)mGridView.getAdapter()).refill(mFriends);
}
Also, check that retrofit is not returning null list.
I have a made a small app when I was learning about all this. It fetches user data from GitHub and shows in a list. I was also working with ORMLite and Picasso so some db stuff is there. Dagger Dependency is also used (but you can ignore that). Here's the link.
Here's how my Presenter behaves:
private DataRetrieverImpl dataRetriever;
#Override
public void getUserList(String name) {
dataRetriever.getUserList(name);
}
#Override
public void onEvent(DataRetrieverEvent event) {
UserList userList = (UserList)event.getData();
mainView.setItems(userList);
}
DataRetrieverImpl works as a module (sort of).
private DataRetriever dataRetriever;
restAdapter = new RestAdapter.Builder().setEndpoint(SERVER_END_POINT).build();
dataRetriever = restAdapter.create(DataRetriever.class);
public void getUserList(final String name) {
Log.i(TAG, "getting user list for: " + name);
Observable<UserList> observable = dataRetriever.getUserList(name);
Log.i(TAG, "subscribe to get userlist");
observable.subscribe(new Action1<UserList>() {
#Override
public void call(UserList userList) {
eventBus.post(new DataRetrieverEvent("UserList", userList));
// save to database
for (User user : userList.getItems()) {
Log.i(TAG, user.getLogin());
try {
dbHelper.create(user);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});
}
And DataRetriever is interface for retrofit. I'm sorry for the naming confusion.
public interface DataRetriever {
#GET("/search/users")
public Observable<UserList> getUserList(#Query("q") String name);
}
Any my Activity,
#Override
public void setItems(final UserList userList) {
runOnUiThread(new Runnable() {
#Override
public void run() {
UserAdapter userAdapter = (UserAdapter)recyclerView.getAdapter();
userAdapter.setUserList(userList);
userAdapter.notifyItemRangeInserted(0, userAdapter.getItemCount());
}
});
}

Categories

Resources