I'm facing the famous IllegalStateException problem in my app. The problem stems after a network request (using Retrofit) is done, returning me an Observable. I'm using the excellent Mosby framework to handle MVP stuff, here is my code:
public class CarAdPresenterCustomer extends CarAdPresenterAbstract {
private final GetCarByIdUseCase getCarByIdUseCase;
private final GetCarMatchDetailsUseCase getCarMatchDetailsUseCaseUseCase;
public CarAdPresenterCustomer(GetCarByIdUseCase getCarByIdUseCase, GetCarMatchDetailsUseCase getCarMatchDetailsUseCaseUseCase) {
this.getCarByIdUseCase = getCarByIdUseCase;
this.getCarMatchDetailsUseCaseUseCase = getCarMatchDetailsUseCaseUseCase;
}
public void getCarMatchDetails(String carId) {
if (isViewAttached()) {
getView().showLoading();
}
getCarMatchDetailsUseCaseUseCase.execute(new GetCarMatchDetailsSubscriber(), new GetCarMatchDetailsUseCase.Params(carId));
}
public void getCarDetails(String carId) {
getCarByIdUseCase.execute(new GetCarByIdSubscriber(), new GetCarByIdUseCase.Params(carId));
}
@Override
public void detachView(boolean retainInstance) {
getCarByIdUseCase.dispose();
getCarMatchDetailsUseCaseUseCase.dispose();
super.detachView(retainInstance);
}
private class GetCarByIdSubscriber extends DefaultObserver<DefaultCarResponse> {
@Override
public void onNext(DefaultCarResponse carResponse) {
if (carResponse != null) {
if (isViewAttached()) {
getView().hideLoading();
getView().inflateCarUiComponents(carResponse.getCar());
}
}
}
@Override
public void onError(Throwable exception) {
if (isViewAttached()) {
getView().hideLoading();
getView().showErrorMessage(exception.getMessage());
}
}
}
The exception I'm getting from Crashlytics is:
Fatal Exception: java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:111)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6843)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
Caused by java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1884)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1902)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:650)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:609)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:143)
at br.com.moobie.android.emailConfirmation.ui.fragment.EmailConfirmationFragment.showEmailConfirmationError(EmailConfirmationFragment.java:202)
at br.com.moobie.android.emailConfirmation.presenter.EmailConfirmationPresenterImpl$GetUserSubscriber.onNext(EmailConfirmationPresenterImpl.java:75)
at br.com.moobie.android.emailConfirmation.presenter.EmailConfirmationPresenterImpl$GetUserSubscriber.onNext(EmailConfirmationPresenterImpl.java:63)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:200)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6843)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
My questions: Is it right to dispose Observables in the detachView method ? From what I understand, the detachView is called when the frag (I'm using it with frags) onDestroyView is called, right after onStop (where the state gets checked by Android). If not, should I create a dispose method in my presenter and call it from my frag onStop ?
Thanks !
I think detachView()
is the correct event to dispose, if you would dispose at onStop()
- where ever your activity stopped the query will cancelled. from UX perspective, that's means that if user started a query, then got a phone call, or switch to another app and comes back, the query will not continue and he must trigger it again and wait inside the app.
as for this problem, even if you would dispose at onStop()
, it will not solve the problem as onSaveInstanceState()
is called before onStop()
.
This is a general common Fragments issue, when you commit fragment transaction after onSaveInstance()
, and should be solved at Fragments level, you can see here various solutions, like using commitAllowingStateLoss()
for instance:
IllegalStateException: Can not perform this action after onSaveInstanceState with ViewPager