My retrofit interface method is returning rx Single with the response
#GET("/data")
Single<Response<List<Foo>>> getData();
and in my Activity onStart() method i call getData() to populate the data and until that am showing a loading Progress it will dismiss on success or fail
getData().observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(() -> showProgress("loading"))
.doOnSuccess(listResponse -> hideProgress())
.subscribe(new SingleSubscriber<Response<List<Foo>>>() {
#Override
public void onSuccess(Response<List<Cat>> response) {
if (response.isSuccessful()) setItems(response.body());
}
#Override
public void onError(Throwable error) {
hideProgress(error.getMessage());
}
})
the first time i start the Activity all goes well the Progress will show and on success or fail it dissmised
The problem is
whenever i start a new Activity via startActivity() and then back to my Activity onStart() is called again and my Retrofit call gets executed again (i did that to keep my data fresh every time i enter my Activity)
but what happens is the Progress is shown till ever, no Success, no Fail happen, i even logged the request i am making on the server and there is no request being requested "at my second time not the first time the Activity opened"
what could the problem be is it in the Retrofit or Rxjava
is there any approaches to do the fresh data thing every time the activity is started or poped from the stack
Are we till ever doomed with the Android activity lifecycle curse...
My sixth sense told me that it is a silly mistake or a misunderstand
i was using a CompositeSubscription to add all my calls to it and unsubscribe all of them safely in onStop() by calling CompositeSubscription.unsubscribe() to prevent resource leaks
what was happening is when i return to my activity i was adding the same subscription to the composite and it was unsubscribed immediately
the docs says :
public void unsubscribe()
Unsubscribes itself and all inner subscriptions.
After call of this method, new Subscriptions added to CompositeSubscription >will be unsubscribed immediately.
and i resolve the problem by calling clear() instead
public void clear()
Unsubscribes any subscriptions that are currently part of this >CompositeSubscription and remove them from the CompositeSubscription so that >the CompositeSubscription is empty and able to manage new subscriptions.
then you are able to add the same subscription again and being called as it normally behave
24 hours lost for that. -_- .
You are not stopping your activity when you return to your activity you need to override your onResume() method. Instead of onCreate() or onStart() call your getData method in onResume(). This will do the job.
Related
I am trying to understand what will happen in case my activity isnt in scope when I get response for the retrofit query. I have a list of items in my activity, and I make a request to server and on response, update a few elements in the list. Below is the code.
I have the below retrofit dependency
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
Call<Output> responseCall = APIInterface.getServerRequest(input);
responseCall.enqueue(new Callback<Output>() {
#Override
public void onResponse(Call<Output> call, Response<Output> response) {
//update the data used by adapter.
adapter.notifyDataSetChanged();
}
#Override
public void onFailure(Call<Output> call, Throwable t) {
//nothing much to do.
}
});
I have a few questions.
When the onResponse/onFailure methods are called, are they called in the main UI thread, or are they still in the background thread?
What will happen if my user has moved out of the existing activity, say they moved on to the next activity, or moved back to previous activity, or closed the app altogether. Will my onResponse still get called? And if my activity isnt on the stack and I refer to some variables in there, will I get nullPointerException?
Is it possible, in the response method, to somehow get the reference to latest activity and then call the notifyDataSetChanged on the (maybe) new instance of activity?
enqueue will do the request on a background thread but the callback will be called on the main thread.
The request can return to an unavailable Activity/Fragment there are a few ways to mitigate problems.
If the call is returning to an Activity you need to check if the Activity is still running. There are a few solutions here. Android: how do I check if activity is running?
If the call is returning to a Fragment you can call isAdded() to check if you can mess with the Fragment.
Not cleanly. But that shouldn't be a thing you want. If you want the request to end up in the new Activity then make the request in that Activity.
Here are a few resources:
If you want to set up a cancelable callback that will not return an output if its canceled.
How to Cancel Retrofit request
If want the request to be lifecycle aware you use LiveData<Response<Output>> from your query. What are the benefits of returning a live data of a retrofit response
Suppose I have
Disposable disposable = signOutUser()
.subscribe((Response<ResponseBody> response) -> {
if (response.isSuccessful()) {
Intent intent = new Intent(view.getContext(), SignInUserActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
view.getContext().startActivity(intent);
((FragmentActivity) view.getContext()).finish();
}
}, (Throwable ex) -> {
Log.e(TAG, "signOutUser: " + ex.getMessage());
});
where signOutUser() returns Single<Response<ResponseBody>>. When signOutUser() is successful, there is an Intent and the current activity is finished(). Otherwise, it fails, possibly due to network error, so there is no intent and the user stays on current activity.
Since this isn't something to observe (it's a one time event, success or fail), and IF the user successfully logs out, then onDestroy will be called which calls compositeDisposable.clear() which clears all the disposables. So then I'm adding a Disposable and immediately that Disposable is being disposed or cleared.
My question is, do I event need to use Composite Disposable? Do I immediately call disposable.dispose() after subscribe? Do I set anything to null? Do I not use Single?
Do I event need to use Composite Disposable?
Yes, you should always use composite disposable (or normal Disposable), and unsubscribe from it when the time comes (onDestroy/onStop whathere you need). The reason for it is that the network call may be finished after you have closed activity, which will result in memory leaks or even crashes (because context will be null).
Do I immediately call disposable.dispose() after subscribe?
No, because this would result in the call to never return a result. If you dispose immediately after calling subscribe, you will never get a response from it. Read about what happens after you dispose an observable.
Do I set anything to null?
No need to. If your single has finished, you don't have to do anything about it. And there won't be any problems that it is still in CompositeDisposable (even if you call dispose on it). Actually, after the Single is finished, RxJava will dispose the observable itself to safe some memory.
Do I not use Single?
Yes, this is perfect situation to use it. You want to perform a single request, no need to use Observable.
Yes, you should use a disposable. Consider the case when the response is received from your API call, but the context is gone for whatever reason. Then, all your code where you are getting the context and calling methods on it would cause an NPE. Disposing properly of this Single would help you avoid this crash.
I have a bottom bar with four tabs. Each tab is a Fragment. I want to stop any network calls when user moves to another Fragment so I'm adding all Observable calls to a CompositeSubscription and I unsubscribe from them in onDestroyView(). Next time user enters the screen all network calls fail (since I have unsubscribed) so I want to subscribe again.
I'm not sure how I am supposed to do this : somehow I have to re-subscribe when onResume()/onViewAttached() is called for the Fragment. I'm using RxJava 1.
Edit : I have checked similar questions and their answers mention cache and replay operators but I don't think that's the case cause they were asking to also get the previously emitted items, while I just want to be able to perform again any network calls.
This is how I'm subscribing to an Observable for a network call :
subscriptions.add(remoteDataSource.getFeedMore(localDataSource.getFirstStoredId())
.doOnNext(new Action1<FeedItemsRequestDetailsWrapper>() {
#Override
public void call(FeedItemsRequestDetailsWrapper wrapper) {
if (wrapper != null) {
localDataSource.saveFeed(wrapper.getFeedItemList());
localDataSource.saveServerState(wrapper.getFeedRequestDetails());
}
}
})
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.mainThread())
.subscribe(new Action1<FeedItemsRequestDetailsWrapper>() {
#Override
public void call(FeedItemsRequestDetailsWrapper wrapper) {
// call to View to update
}
}));
And how I unsubscribe :
#Override
public void unsubscribe() {
if (subscriptions != null && subscriptions.hasSubscriptions()) {
subscriptions.unsubscribe();
}
}
Example Use Case : user enters Timeline screen, user clicks a button and a network call is executed ( modeled as an Observable in my Presenter class like the code I posted right above ). Then user exits this screen (
onDestroyView() is called and any subscriptions are unsubscribed). Some time later user enters Timeline screen again and clicks the button.
This is when I receive HTTP FAILED: java.io.IOException: Canceled cause I have unsubscribed and I want to re-subscribe again so I can execute the network call without errors.
Thanks
Update
If you call unsubscribe in CompositeSubscrition you can't add new subscriptions to it again. If you want to use the same composite instance again, then you need to call subscriptions.clear() or you can create a new instance when the fragment is initialized.
Prev
First things first, if you unsubscribe from any observable/stream/flowable etc. you gonna lose the any incoming data/events.
If you want to get the latest result of an subscription then obviously you should do it before unsubscribe happens.
The problem here is your subscriptions should not be dependant on any Fragment or Activity lifecycle unless it's totally finished/destroyed.
So if you know that you have long requests you should subscribe them on a android.app.Service.
Then you will face another problem communicating back and forth between Services and Fragments/Activities.
The easy solution on your case is you can create a BehaviourSubject in a singleton class (better to use Dagger to inject that model to both fragment and service). Then in your Service subscribe to your long running stream and publish the next events to that BehaviourSubject
BehaviourSubject saves the last emitted data. So next time you subscribe them in your newly created fragment it will start with the last emitted item.
Of course this answer just cover one use-case according to your needs you may need to do something else.
In my application, some Activities get information from the server and I need to ensure that the connection was already established before trying to retrieve the data. I'm using a BehaviorSubject to notify the subscribers when the connection is established, so the Activity can load the data.
The issue is that more than one activity in the same flow have the same behavior. In our API we must call connect() in the onStart() and disconnect() in onStop, but if the user goes the another Activity that also uses connection, there's no need to recreate the connection, we could use the same.
At the moment, I'm implementing in the following way:
When you call connect() it returns an BehaviorSubject that will be subscribed from the calling class
On method disconnect(), it only actually disconnects if there is no observers in the BehaviorSubject, indicating that no Activities are waiting for the response.
The calling class must dispose the Observable before calling the disconnect(), otherwise the method hasObservers() will never return false
#CheckResult
#Override
public BehaviorSubject<Boolean> connect() {
if (!connectionManager.isConnected()) {
connectionManager.connect(TIMEOUT);
}
return mSubject;
}
#Override
public void disconnect() {
if (connectionManager.isConnected() && !mSubject.hasObservers()){
connectionManager.disconnect();
}
}
In my previous implementation, I was using listeners to achieve this. Every time connect() is called, it must receive the listener as a parameter that will be added in a Array of listeners and later notified one by one when the connection was established.
And every time disconnect() is called it also must receive the listener as a parameter to be removed from the list. The connectionManager.disconnect() will only be called if the Array of listeners is null, indicating that no Activities are waiting for the response.
Is there a better way to handle this?
Where you create your subject, you can specify what needs to happen when someone subscribes/unsubscribes:
subject.doOnUnsubscribe(()-> {
if(!subject.hasObservers()) {
closeConnection();
}
});
subject.doOnSubscribe(() -> {
openConnectionIfNotOpen();
});
This way, you can get rid of the disconnect() call, you just have to unsubscribe
I'm realizing while reviewing some of my existing Activity code that some of the Volley async network response handlers call finish() to return to the caller (who typically start my Activity via startActivityForResult). Something like this:
private Response.Listener<MyResponse> mResponseListener = new Response.Listener<MyResponse>(){
#Override public void onResponse(MyResponse myResponse) {
// I could get here *after* rotation was initiated
// do stuff
finish();
};
}
After some recent investigation into a bug, I realized my code does not handle the rotation case properly -- I have cases where a network response handler could, in theory, be called in between activity instance A1's destruction and activity instance A2's creation when the device is rotated. What effect does calling finish() after A1's onDestroy is called have? Is the fact that "we're done" lost? Does A2 get created and stick around as though finish were never called?
What effect does calling finish() after A1's onDestroy is called have?
I'm not sure, but to me it seems like it should finish the activity and remove it from the task's back stack.
I think the root of your problem lies elsewhere, though - Activities and Fragments really aren't the proper place to be handling network or other asynchronous operations. You should treat Activities and Fragments more like dumb Views in terms of an MVC or MVP design - they should accept some data from a controller and render their Views accordingly.
Hence, the proper place for making async requests would be something like a plain Java class with an instance which is kept in the Application context, rather than in any Activity context. This way, the object that is performing your async operation is not bound to the fickle Activity/Fragment lifecycle.
For example, I like using EventBus with its cross-thread sticky events to communicate between the object making the request and the Activity that has to display the result, subscribing an event listener in the Activity's onResume callback and unsubscribing in onPause.
An article that helped me tremendously in learning to think of Activities and Fragments was this one by Matt Swanson.
I believe what happens after onDestroy is that the Activity reaches the end of its lifecycle and is soon garbage collected.
I can think of two solutions for your case
Cancel all Volley Requests in onDestroy. You can use RequestQueue#cancelAll which cancels all requests given a specific tag. This tag is attached when instantiating a Volley Request.
Use Publisher-Subsriber pattern such as provided by Otto or EventBus. I'll just demonstrate the magic of Otto.
public static class ResponseEvent{
MyResponse myResponse;
public ResponseEvent(MyResponse myResponse){
this.myResponse = myResponse;
}
}
public void onCreate(){
BusProvider.getInstance().register(this);// register this activity as subscriber
}
public void onDestroy(){
BusProvider.getInstance().unregister(this);
}
// it is up you where you want to register and unregister depending
// whether you want to digest the response when the activity
// is on foreground or background as well.
private Response.Listener<MyResponse> mResponseListener = new Response.Listener<MyResponse>(){
#Override public void onResponse(MyResponse myResponse) {
// I could get here *after* rotation was initiated
// do stuff
BusProvider.getInstance().post(new ResponseEvent(myResponse));
//sends this event to previously registered subcsriber.
//The subscriber method will be active as long the activity
//hasn't been de-registered.
//Nothing will happen after unregister has been called.
}};
//this is the subscriber method that will digest your response
#Subsribe
public void onGotResponse(ResponseEvent event){
// do your stuff with response here.
finish();
// notice I moved finish() here so you can be sure
// it will only be called as long as the activity is still active
}