Multiple async calls best practices in Android - java

I need a shorter & cleaner solution for Example 1. So multiple async calls need to be finished before a certain Activity/Fragment can start. Example 1 is very messy and ugly with member bools, but works.
I was considering using the Google Tasks API. But for that I need to add a google-services.json and connect to either "Google Sign-in", "Analytics" or "Cloud messaging", which I don't need I think. There must be a better way or is this the correct way to go?
Example 1:
boolean mIsFirstDone = false;
boolean mIsSecondDone = false;
boolean mAlreadyDone = false;
private void prepareSomeData() {
dataManager.requestSomeContent(new ApiCallback() {
#Override
public void onSuccess(final Object object) {
mIsFirstDone = true;
if(mIsFirstDone && mIsSecondDone && !mAlreadyDone) {
mAlreadyDone = true;
doSomething();
}
}
});
}
private void prepareSomeSettings() {
dataManager.requestSomeSettings(new ApiCallback() {
#Override
public void onSuccess(final Object object) {
mIsSecondDone = true;
if(mIsFirstDone && mIsSecondDone && !mAlreadyDone) {
mAlreadyDone = true;
doSomething();
}
}
});
}
With Tasks API:
Tasks.whenAll(SomeDataTask, SomeSettingsTask).addOnSuccessListener(executor, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void v) {
doSomething();
}
}).addOnFailureListener(executor, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});

RxJava, as pointed out, is probably a better solution to this. The reason why is because you can chain multiple api requests, database requests into a concrete block of code that looks elegant and clean. As an example, see below of what I'm trying to say:
Subscription subscription = apiService.getUser(someId)
.flatMap(user -> apiService.getFavourites(user.getFavouritesTag())
.subscribe(favourites -> view.updateFavouritesList(favourites),
throwable -> Log.e(TAG, throwable.printStackTrace());

Have you considered learning about RxJava and reformatting all your projects to RxJava along with retrofit for API?
start with something like this:
https://medium.com/yammer-engineering/chaining-multiple-sources-with-rxjava-20eb6850e5d9
https://adityaladwa.wordpress.com/2016/05/11/dagger-2-and-mvp-architecture/

Related

How to convert a pre-instanced callback into Observable

I'm fairly new to RxJava and I have a basic understanding as to how to wrap a callback into an Observable but what I'm having difficulty with is doing so when the callback/listener is pre-instanced. Every example that I have found only shows instancing the callback directly into the Observable being created.
Some example code of what I'm talking about. I'm working with an Api that's works like this:
public class Api {
private ApiCallback callback;
void initialize(ApiCallback callback){
this.callback = callback;
}
void doAction1(){
this.callback.onAction1Complete();
}
}
interface ApiCallback {
void onInitialized();
void onAction1Complete();
}
With the real api I am working with I have no control over how it works so I must work with it in this state. In terms of trying to work with this Api using observables here is the struggle I am having. I have a member variable that holds the Api object:
private Api mApi = new Api();
Now in order to initialize this I have one of two options it seems.
Option 1:
Completable startApi() {
return Completable.create(new CompletableOnSubscribe() {
#Override
public void subscribe(final CompletableEmitter emitter) throws Exception {
mApi.initialize(new ApiCallback() {
#Override
public void onInitialized() {
emitter.onComplete();
}
#Override
public void onAction1Complete() {
}
});
}
});
}
Option 2:
private ApiCallback premadeCallback = new ApiCallback() {
#Override
public void onInitialized() {
}
#Override
public void onAction1Complete() {
}
};
Completable startApi() {
return Completable.create(new CompletableOnSubscribe() {
#Override
public void subscribe(final CompletableEmitter emitter) throws Exception {
mApi.initialize(premadeCallback);
}
});
}
Now the issue I have is that Option 2 makes more sense to me when I need to know when the other methods in the callback are called from Api calls. With my understanding of RxJava however I don't understand how I can reach these method calls with an Api that works like this.
For example:
Completable doAction1() {
return Completable.create(new CompletableOnSubscribe() {
#Override
public void subscribe(final CompletableEmitter emitter) throws Exception {
// Api is already initialized with callback
// How do I reach the callback from here?
}
});
}
The only what that I can currently think of as to how to achieve this would be to create a member variable as an emitter (or a dictionary of emitters) and then call its appropriate method in the api callback when needed. My concerns with this are A. I'm unsure if RxJava can work this way B. This sounds like a terrible idea.

Returning a Completable/Observables but checking is first - Flatmap?

My Reactive knowledge is very basic and I was wondering what the right way would be if I like to return an observable from a function which is using an observable. I wanna extend the observable which I am calling with a check.
In my example, I think it is a lot of code for not much. I think I would also need to worry about the disposable of the inner observable. Do I?
public Completable updateUserPhotoURL(Uri photoURL, UserProfileChangeRequest profileUpdates) {
return Completable.create(emitter -> {
if (mFirebaseUser == null) {
emitter.onError(new Exception("Firebase User is not initiated"));
}
RxFirebaseUser.updateProfile(mFirebaseUser, profileUpdates).complete()
.subscribe(new DisposableCompletableObserver() {
#Override
public void onComplete() {
emitter.onComplete();
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
emitter.onError(e);
}
});
});
}
What would be the right (more elegant) way of doing so?

RxJava + Retrofit + Realm is doing unlimited get request

I am completely new to rxJava and it's really confusing, I want to make my app offline first and I've decided to use Realm and Retrofit, First I want to get the data from retrofit and then get the data from my remote webservice then, use realm's insertOrUpdate to merge the remote objects with the local one. I'm able to get on this process so far but when I looked into my Network requests on stetho, this method is complete requesting infinite times. Where did I go wrong? Here's the function
public Observable<RealmResults<Event>> all() {
Realm realm = Realm.getDefaultInstance();
return realm.where(Event.class).findAllAsync()
.asObservable()
.filter(new Func1<RealmResults<Event>, Boolean>() {
#Override
public Boolean call(RealmResults<Event> events) {
return events.isLoaded();
}
})
.doOnNext(new Action1<RealmResults<Event>>() {
#Override
public void call(RealmResults<Event> events) {
service.getEvents()
.subscribeOn(Schedulers.io())
.subscribe(new Action1<List<Event>>() {
#Override
public void call(final List<Event> events) {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.insertOrUpdate(events);
}
});
} // auto-close
}
});
}
});
}
and here's the function on my activity, where I use it
private void getEvents() {
Log.i("EVENTSELECTION", "STARTING");
repository.all()
.subscribe(new Subscriber<List<Event>>() {
#Override
public void onCompleted() {
Log.i("EVENTSELECTION", "Task Completed");
swipeRefreshLayout.setRefreshing(false);
}
#Override
public void onError(Throwable e) {
Log.e("EVENTSELECTION", e.getMessage());
swipeRefreshLayout.setRefreshing(false);
e.printStackTrace();
}
#Override
public void onNext(List<Event> events) {
Log.i("EVENTSELECTION", String.valueOf(events.size()));
}
});
}
Thank you so much.
Where did I go wrong?
Let's go through it:
1.
public Observable<RealmResults<Event>> all() {
Realm realm = Realm.getDefaultInstance();
This opens a Realm instance that will never be closed. So your Realm lifecycle management is wrong, refer to the documentation for best practices.
2.
return realm.where(Event.class).findAllAsync()
.asObservable() // <-- listens for changes in the Realm
// ...
.doOnNext(new Action1<RealmResults<Event>>() {
#Override
public void call(RealmResults<Event> events) {
service.getEvents() // <-- downloads data
.subscribeOn(Schedulers.io())
.subscribe(new Action1<List<Event>>() {
You basically say that "in case there are any changes made to data in Realm, then download data from the service and write it into the Realm"
Which will trigger the RealmChangeListener which will trigger a download and so on.
This is a conceptual error, you're using Realm notifications incorrectly.
RealmResults<T> is not just a list of objects, it is also a subscription for changes. So you need to keep it as a field reference, and "stay subscribed to changes in the database".
RealmResults<Sth> results;
RealmChangeListener<RealmResults<Sth>> changeListener = (element) -> {
if(element.isLoaded()) {
adapter.updateData(element);
}
};
void sth() {
results = realm.where(Sth.class).findAllSortedAsync("id");
results.addChangeListener(changeListener);
}
void unsth() {
if(results != null && results.isValid()) {
results.removeChangeListener(changeListener);
results = null;
}
}
In your case, RealmResults<T> which symbolizes a subscription and also provides access to the current/new data is wrapped as an Observable<T> which you can create subscribers to.
Observable<List<<Sth>> results;
Subscription subscription;
Action1<List<Sth>> changeListener = (element) -> {
if(element.isLoaded()) {
adapter.updateData(element);
}
};
void sth() {
results = realm.where(Sth.class).findAllSortedAsync("id").asObservable();
subscription = results.subscribe(changeListener);
}
void unsth() {
if(subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
subscription = null;
results = null;
}
}
As you can see, you have a subscription at the start of the component, and an unsubscription at the end of the component.
Calling Observable.first() is incorrect, it does not make sense to do that. If you saw it in any tutorial (I've seen it before...), then that tutorial was wrong.
So it's really a by design on realm and it won't call the onCompleted, I added a .first() at the end of my getEvents function to get only the first result.

RxJava retryWhen resubscribe propagation

I'm using Retrofit with RxJava in an Android app for communications and have to handle error on parsing the response from a seemly ok HTTP response (status 200 code).
I have also implemented a way of handling the error using retryWhen operator which is connected to user's input to decide whether to retry it or not. This works by resubscribing to the original Observable.
The first approach I have tried was to have something like this:
services.getSomething()
.map(response -> {
if (checkBadResponse(response)) {
throw new RuntimeException("Error on service");
} else {
return parseResponse(response);
}
}).retryWhen(this::shouldRetry);
With this the service is not called again. It seems the retryWhen operator cannot resubscribe to the service's Observable.
What end up working was implementing another operator which doesn't send the onCompleted forward and use it with lift like the following:
public class CheckResponseStatus<T> implements Observable.Operator<ResponsePayload<T>, ResponsePayload<T>> {
#Override
public Subscriber<? super ResponsePayload<T>> call(Subscriber<? super ResponsePayload<T>> subscriber) {
return new Subscriber<ResponsePayload<T>>() {
private boolean hasError = false;
#Override
public void onCompleted() {
if (!hasError)
subscriber.onCompleted();
}
#Override
public void onError(Throwable e) {
hasError = true;
subscriber.onError(e);
}
#Override
public void onNext(ResponsePayload<T> response) {
if (response.isOk()) {
subscriber.onNext(response);
} else {
hasError = true;
subscriber.onError(new RuntimeException(response.getMessage()));
}
}
};
}
}
Using it like:
services.getSomething()
.lift(new CheckResponseStatus())
.map(response -> parseResponse(response))
.retryWhen(this::shouldRetry);
Is this the correct way of dealing with it or is there a simpler, better way?
It's looks like a bug in rx-java implementation. Anyway, throwing an exception from map function is a bad thing since the function is supposed to be pure (e.g. without side effects). You should use a flatMap operator in your case:
services.getSomething()
.flatMap(response -> {
if (checkBadResponse(response)) {
return Observable.<ResponseType>error(new RuntimeException("Error on service"));
} else {
return Observable.<ResponseType>just(parseResponse(response);
}
}).retryWhen(this::shouldRetry);
The code above works as expected and really retries the request if error occurs.

If ready do else add to queue

I'm using a complex closed api. I want to create a super simple way to use it.
Basicaly, it has the following behaviour:
boolean everythingReady = false;
API.start(new Callback() {
public void onReady()
{
API.invite(new Callback2() {
public void onReady()
{
everythingReady = true;
}
});
}
});
while (!everythingReady); // Wait
API.send("hello);
API.send("What's up");
This is a chat API and the above code is "pseudo" java.
What I want to do now is:
API.start();
API.invite();
API.send("Hello);
API.send("What's up");
these methods would wait until each above has been correctly loaded (onReady called) to run. (eg: if (!apiStarted) addToQueue else do invite)
Is there a way to do that in java (as I can't edit at all the API sources).
Thanks
How about:
API.start(new Callback() {
public void onReady()
{
API.invite(new Callback() {
public void onReady()
{
API.send("hello");
API.send("What's up");
}
});
}
});
This way the send methods will be called only after invite is ready.

Categories