I followed several tutorials and open sources on the web and built simple app based on MVP architecture using RxJava, Dagger 2 and Retrofit. Everything works fine except when I start downloading data and immediately rotate the screen previous request is being canceled and new request is being made.
The reason why network request is being canceled is that I'm unsubscribing from Observable inside onDestroyView
of my View. That is to prevent memory leak!
How can I retain previous network request not letting Subscription
to leak?
Here's View:
public class MoviesFragment extends Fragment implements MoviesView{
@Inject
MoviesPresenter moviesPresenter;
//....
public MoviesFragment(){
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
setRetainInstance(true);
((BaseApplication) getActivity().getApplication()).createListingComponent().inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_movies, container, false);
ButterKnife.bind(this, rootView);
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
moviesPresenter.setView(this);
}
// ....
@Override
public void onDestroyView()
{
super.onDestroyView();
moviesPresenter.destroy();
ButterKnife.unbind(this);
}
@Override
public void onDetach()
{
super.onDetach();
}
@Override
public void onDestroy()
{
super.onDestroy();
((BaseApplication)getActivity().getApplication()).releaseListingComponent();
}
//....
}
Here's Presenter:
public class MoviesPresenterImpl implements MoviesPresenter {
private final MoviesInteractor moviesInteractor;
private MoviesView view;
private Subscription fetchSubscription;
public MoviesPresenterImpl(MoviesInteractor moviesInteractor) {
this.moviesInteractor = moviesInteractor;
}
@Override
public void downloadMovies() {
fetchSubscription = moviesInteractor.getMovieList(new MoviesInteractorImpl.GetMovieListCallback() {
@Override
public void onSuccess(List<MovieModel> movieModels) {
onMovieFetchSuccess(movieModels);
}
@Override
public void onError(NetworkError networkError) {
onMovieFetchFailed(new Throwable(networkError));
}
});
}
@Override
public void setView(MoviesView view) {
this.view = view;
downloadMovies();
}
@Override
public void destroy() {
view = null;
fetchSubscription.unsubscribe();
}
private void onMovieFetchSuccess(List<MovieModel> movies) {
if (isViewAttached()) {
view.showMovies(movies);
}
}
//....
}
Here's Interactor between API and Presenter:
public class MoviesInteractorImpl implements MoviesInteractor {
private Observable<MoviesResponseModel> call;
public MoviesInteractorImpl(MoviesRetrofitService moviesRetrofitService) {
call = moviesRetrofitService.getMovies("en", "popularity.desc", "MY_API_KEY");
}
@Override
public Subscription getMovieList(final GetMovieListCallback callback) {
return call
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MoviesResponseModel>() {
@Override
public void onStart() {
super.onStart();
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
callback.onError(new NetworkError(e));
}
@Override
public void onNext(MoviesResponseModel cityListResponse) {
callback.onSuccess(cityListResponse.getMovieList());
}
});
}
public interface GetMovieListCallback {
void onSuccess(List<MovieModel> movieModels);
void onError(NetworkError networkError);
}
}
Custom Scope so that Presenter stays as long as View is alive.
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ListingScope {
}
Dagger Module:
@Module
public class ListingModule {
@Provides
public MoviesRetrofitService provideMoviesRetorfitService(Retrofit retrofit) {
return retrofit.create(MoviesRetrofitService.class);
}
@Provides
MoviesInteractor provideMoviesInteractor(MoviesRetrofitService moviesRetrofitService){
return new MoviesInteractorImpl(moviesRetrofitService);
}
@Provides
MoviesPresenter provideMoviesPresenter(MoviesInteractor moviesInteractor){
return new MoviesPresenterImpl(moviesInteractor);
}
}
Dagger Component - Subcomponent:
@ListingScope
@Subcomponent(modules = {ListingModule.class})
public interface ListingComponent {
MoviesFragment inject(MoviesFragment moviesFragment);
}