Fix crash(es) caused by failing to dispose Rx observables (#2669)

This commit is contained in:
Dmitry Brant 2019-03-19 17:08:04 -04:00 committed by Adam Jones
parent 38d39e08ac
commit 8474c04c64
21 changed files with 121 additions and 86 deletions

View file

@ -44,6 +44,8 @@ import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.upload.FileUtils; import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ConfigUtils;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.internal.functions.Functions;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -128,6 +130,9 @@ public class CommonsApplication extends Application {
createNotificationChannel(this); createNotificationChannel(this);
// This handler will catch exceptions thrown from Observables after they are disposed,
// or from Observables that are (deliberately or not) missing an onError handler.
RxJavaPlugins.setErrorHandler(Functions.emptyConsumer());
if (setupLeakCanary() == RefWatcher.DISABLED) { if (setupLeakCanary() == RefWatcher.DISABLED) {
return; return;

View file

@ -142,6 +142,12 @@ public class AchievementsActivity extends NavigationBaseActivity {
initDrawer(); initDrawer();
} }
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
}
/** /**
* To invoke the AlertDialog on clicking info button * To invoke the AlertDialog on clicking info button
*/ */
@ -238,12 +244,12 @@ public class AchievementsActivity extends NavigationBaseActivity {
if (StringUtils.isNullOrWhiteSpace(userName)) { if (StringUtils.isNullOrWhiteSpace(userName)) {
return; return;
} }
okHttpJsonApiClient.getWikidataEdits(userName) compositeDisposable.add(okHttpJsonApiClient.getWikidataEdits(userName)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(edits -> wikidataEditsText.setText(String.valueOf(edits)), e -> { .subscribe(edits -> wikidataEditsText.setText(String.valueOf(edits)), e -> {
Timber.e("Error:" + e); Timber.e("Error:" + e);
}); }));
} }
private void showSnackBarWithRetry() { private void showSnackBarWithRetry() {

View file

@ -58,13 +58,12 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
* Makes API call to check if user is blocked from Commons. If the user is blocked, a snackbar * Makes API call to check if user is blocked from Commons. If the user is blocked, a snackbar
* is created to notify the user * is created to notify the user
*/ */
protected void showBlockStatus() protected void showBlockStatus() {
{ compositeDisposable.add(Observable.fromCallable(() -> mediaWikiApi.isUserBlockedFromCommons())
Observable.fromCallable(() -> mediaWikiApi.isUserBlockedFromCommons())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.filter(result -> result) .filter(result -> result)
.subscribe(result -> ViewUtil.showShortSnackbar(findViewById(android.R.id.content), R.string.block_notification) .subscribe(result -> ViewUtil.showShortSnackbar(findViewById(android.R.id.content), R.string.block_notification)
); ));
} }
} }

View file

@ -52,6 +52,7 @@ import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -83,6 +84,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
ProgressDialog progressDialog; ProgressDialog progressDialog;
private AppCompatDelegate delegate; private AppCompatDelegate delegate;
private LoginTextWatcher textWatcher = new LoginTextWatcher(); private LoginTextWatcher textWatcher = new LoginTextWatcher();
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private Boolean loginCurrentlyInProgress = false; private Boolean loginCurrentlyInProgress = false;
private Boolean errorMessageShown = false; private Boolean errorMessageShown = false;
@ -195,6 +197,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@Override @Override
protected void onDestroy() { protected void onDestroy() {
compositeDisposable.clear();
try { try {
// To prevent leaked window when finish() is called, see http://stackoverflow.com/questions/32065854/activity-has-leaked-window-at-alertdialog-show-method // To prevent leaked window when finish() is called, see http://stackoverflow.com/questions/32065854/activity-has-leaked-window-at-alertdialog-show-method
if (progressDialog != null && progressDialog.isShowing()) { if (progressDialog != null && progressDialog.isShowing()) {
@ -219,10 +222,10 @@ public class LoginActivity extends AccountAuthenticatorActivity {
String twoFactorCode = twoFactorEdit.getText().toString(); String twoFactorCode = twoFactorEdit.getText().toString();
showLoggingProgressBar(); showLoggingProgressBar();
Observable.fromCallable(() -> login(username, password, twoFactorCode)) compositeDisposable.add(Observable.fromCallable(() -> login(username, password, twoFactorCode))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> handleLogin(username, rawUsername, password, result)); .subscribe(result -> handleLogin(username, rawUsername, password, result)));
} }
private String login(String username, String password, String twoFactorCode) { private String login(String username, String password, String twoFactorCode) {

View file

@ -30,6 +30,7 @@ import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -41,6 +42,7 @@ public class BookmarkPicturesFragment extends DaggerFragment {
private static final int TIMEOUT_SECONDS = 15; private static final int TIMEOUT_SECONDS = 15;
private GridViewAdapter gridAdapter; private GridViewAdapter gridAdapter;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@BindView(R.id.statusMessage) TextView statusTextView; @BindView(R.id.statusMessage) TextView statusTextView;
@BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar; @BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar;
@ -82,6 +84,12 @@ public class BookmarkPicturesFragment extends DaggerFragment {
controller.stop(); controller.stop();
} }
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@ -113,11 +121,11 @@ public class BookmarkPicturesFragment extends DaggerFragment {
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
statusTextView.setVisibility(GONE); statusTextView.setVisibility(GONE);
Observable.fromCallable(() -> controller.loadBookmarkedPictures()) compositeDisposable.add(Observable.fromCallable(() -> controller.loadBookmarkedPictures())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.subscribe(this::handleSuccess, this::handleError); .subscribe(this::handleSuccess, this::handleError));
} }
/** /**

View file

@ -31,6 +31,7 @@ import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -51,6 +52,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
@BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar; @BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar;
@BindView(R.id.categoryImagesList) GridView gridView; @BindView(R.id.categoryImagesList) GridView gridView;
@BindView(R.id.parentLayout) RelativeLayout parentLayout; @BindView(R.id.parentLayout) RelativeLayout parentLayout;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private boolean hasMoreImages = true; private boolean hasMoreImages = true;
private boolean isLoading = true; private boolean isLoading = true;
private String categoryName = null; private String categoryName = null;
@ -74,6 +76,12 @@ public class CategoryImagesListFragment extends DaggerFragment {
initViews(); initViews();
} }
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
}
/** /**
* Initializes the UI elements for the fragment * Initializes the UI elements for the fragment
* Setup the grid view to and scroll listener for it * Setup the grid view to and scroll listener for it
@ -109,11 +117,11 @@ public class CategoryImagesListFragment extends DaggerFragment {
isLoading = true; isLoading = true;
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
Observable.fromCallable(() -> controller.getCategoryImages(categoryName)) compositeDisposable.add(Observable.fromCallable(() -> controller.getCategoryImages(categoryName))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.subscribe(this::handleSuccess, this::handleError); .subscribe(this::handleSuccess, this::handleError));
} }
/** /**
@ -215,11 +223,11 @@ public class CategoryImagesListFragment extends DaggerFragment {
} }
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
Observable.fromCallable(() -> controller.getCategoryImages(categoryName)) compositeDisposable.add(Observable.fromCallable(() -> controller.getCategoryImages(categoryName))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.subscribe(this::handleSuccess, this::handleError); .subscribe(this::handleSuccess, this::handleError));
} }
/** /**

View file

@ -97,17 +97,17 @@ public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
} }
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
if (!isParentCategory){ if (!isParentCategory){
Observable.fromCallable(() -> mwApi.getSubCategoryList(categoryName)) compositeDisposable.add(Observable.fromCallable(() -> mwApi.getSubCategoryList(categoryName))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.subscribe(this::handleSuccess, this::handleError); .subscribe(this::handleSuccess, this::handleError));
}else { }else {
Observable.fromCallable(() -> mwApi.getParentCategoryList(categoryName)) compositeDisposable.add(Observable.fromCallable(() -> mwApi.getParentCategoryList(categoryName))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.subscribe(this::handleSuccess, this::handleError); .subscribe(this::handleSuccess, this::handleError));
} }
} }

View file

@ -61,7 +61,6 @@ import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -101,7 +100,6 @@ public class ContributionsFragment
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView; @BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
@BindView(R.id.campaigns_view) CampaignView campaignView; @BindView(R.id.campaigns_view) CampaignView campaignView;
private Disposable placesDisposable;
private LatLng curLatLng; private LatLng curLatLng;
private boolean firstLocationUpdate = true; private boolean firstLocationUpdate = true;
@ -592,7 +590,7 @@ public class ContributionsFragment
private void updateClosestNearbyCardViewInfo() { private void updateClosestNearbyCardViewInfo() {
curLatLng = locationManager.getLastLocation(); curLatLng = locationManager.getLastLocation();
placesDisposable = Observable.fromCallable(() -> nearbyController compositeDisposable.add(Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, curLatLng, true, false)) // thanks to boolean, it will only return closest result .loadAttractionsFromLocation(curLatLng, curLatLng, true, false)) // thanks to boolean, it will only return closest result
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -600,7 +598,7 @@ public class ContributionsFragment
throwable -> { throwable -> {
Timber.d(throwable); Timber.d(throwable);
updateNearbyNotification(null); updateNearbyNotification(null);
}); }));
} }
private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) { private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
@ -635,10 +633,6 @@ public class ContributionsFragment
isUploadServiceConnected = false; isUploadServiceConnected = false;
} }
} }
if (placesDisposable != null) {
placesDisposable.dispose();
}
} }
@Override @Override

View file

@ -307,11 +307,11 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void setNotificationCount() { private void setNotificationCount() {
Observable.fromCallable(() -> notificationController.getNotifications(false)) compositeDisposable.add(Observable.fromCallable(() -> notificationController.getNotifications(false))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::initNotificationViews, .subscribe(this::initNotificationViews,
throwable -> Timber.e(throwable, "Error occurred while loading notifications")); throwable -> Timber.e(throwable, "Error occurred while loading notifications")));
} }
private void initNotificationViews(List<Notification> notificationList) { private void initNotificationViews(List<Notification> notificationList) {

View file

@ -9,18 +9,27 @@ import androidx.fragment.app.Fragment;
import dagger.android.AndroidInjector; import dagger.android.AndroidInjector;
import dagger.android.DispatchingAndroidInjector; import dagger.android.DispatchingAndroidInjector;
import dagger.android.support.HasSupportFragmentInjector; import dagger.android.support.HasSupportFragmentInjector;
import io.reactivex.disposables.CompositeDisposable;
public abstract class CommonsDaggerSupportFragment extends Fragment implements HasSupportFragmentInjector { public abstract class CommonsDaggerSupportFragment extends Fragment implements HasSupportFragmentInjector {
@Inject @Inject
DispatchingAndroidInjector<Fragment> childFragmentInjector; DispatchingAndroidInjector<Fragment> childFragmentInjector;
protected CompositeDisposable compositeDisposable = new CompositeDisposable();
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
inject(); inject();
super.onAttach(context); super.onAttach(context);
} }
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
}
@Override @Override
public AndroidInjector<Fragment> supportFragmentInjector() { public AndroidInjector<Fragment> supportFragmentInjector() {
return childFragmentInjector; return childFragmentInjector;

View file

@ -98,7 +98,7 @@ public class SearchActivity extends NavigationBaseActivity implements MediaDetai
viewPagerAdapter.setTabData(fragmentList, titleList); viewPagerAdapter.setTabData(fragmentList, titleList);
viewPagerAdapter.notifyDataSetChanged(); viewPagerAdapter.notifyDataSetChanged();
RxSearchView.queryTextChanges(searchView) compositeDisposable.add(RxSearchView.queryTextChanges(searchView)
.takeUntil(RxView.detaches(searchView)) .takeUntil(RxView.detaches(searchView))
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -120,7 +120,7 @@ public class SearchActivity extends NavigationBaseActivity implements MediaDetai
searchHistoryContainer.setVisibility(View.VISIBLE); searchHistoryContainer.setVisibility(View.VISIBLE);
} }
} }
); ));
} }
/** /**

View file

@ -3,7 +3,6 @@ package fr.free.nrw.commons.explore.categories;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -136,12 +135,12 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
progressBar.setVisibility(GONE); progressBar.setVisibility(GONE);
queryList.clear(); queryList.clear();
categoriesAdapter.clear(); categoriesAdapter.clear();
Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size())) compositeDisposable.add(Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size()))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.doOnSubscribe(disposable -> saveQuery(query)) .doOnSubscribe(disposable -> saveQuery(query))
.subscribe(this::handleSuccess, this::handleError); .subscribe(this::handleSuccess, this::handleError));
} }
@ -152,11 +151,11 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
this.query = query; this.query = query;
bottomProgressBar.setVisibility(View.VISIBLE); bottomProgressBar.setVisibility(View.VISIBLE);
progressBar.setVisibility(GONE); progressBar.setVisibility(GONE);
Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size())) compositeDisposable.add(Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size()))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.subscribe(this::handlePaginationSuccess, this::handleError); .subscribe(this::handlePaginationSuccess, this::handleError));
} }
/** /**

View file

@ -4,7 +4,6 @@ package fr.free.nrw.commons.explore.images;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -142,12 +141,12 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
bottomProgressBar.setVisibility(GONE); bottomProgressBar.setVisibility(GONE);
queryList.clear(); queryList.clear();
imagesAdapter.clear(); imagesAdapter.clear();
okHttpJsonApiClient.searchImages(query, queryList.size()) compositeDisposable.add(okHttpJsonApiClient.searchImages(query, queryList.size())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.doOnSubscribe(disposable -> saveQuery(query)) .doOnSubscribe(disposable -> saveQuery(query))
.subscribe(this::handleSuccess, this::handleError); .subscribe(this::handleSuccess, this::handleError));
} }
@ -159,11 +158,11 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
this.query = query; this.query = query;
bottomProgressBar.setVisibility(View.VISIBLE); bottomProgressBar.setVisibility(View.VISIBLE);
progressBar.setVisibility(GONE); progressBar.setVisibility(GONE);
okHttpJsonApiClient.searchImages(query, queryList.size()) compositeDisposable.add(okHttpJsonApiClient.searchImages(query, queryList.size())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.subscribe(this::handlePaginationSuccess, this::handleError); .subscribe(this::handlePaginationSuccess, this::handleError));
} }
/** /**

View file

@ -420,7 +420,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private void onDeleteClicked(Spinner spinner) { private void onDeleteClicked(Spinner spinner) {
String reason = spinner.getSelectedItem().toString(); String reason = spinner.getSelectedItem().toString();
Single<String> deletionReason = reasonBuilder.getReason(media, reason); Single<String> deletionReason = reasonBuilder.getReason(media, reason);
deletionReason compositeDisposable.add(deletionReason
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> { .subscribe(s -> {
@ -428,7 +428,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
deleteTask.execute(); deleteTask.execute();
isDeleted = true; isDeleted = true;
enableDeleteButton(false); enableDeleteButton(false);
}); }));
} }
@OnClick(R.id.seeMore) @OnClick(R.id.seeMore)

View file

@ -42,7 +42,6 @@ import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener; import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -85,8 +84,6 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet
private LatLng curLatLng; private LatLng curLatLng;
private Disposable placesDisposable;
private Disposable placesDisposableCustom;
private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
public View view; public View view;
private Snackbar snackbar; private Snackbar snackbar;
@ -298,7 +295,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
bundle.clear(); bundle.clear();
bundle.putString("CurLatLng", gsonCurLatLng); bundle.putString("CurLatLng", gsonCurLatLng);
placesDisposable = Observable.fromCallable(() -> nearbyController compositeDisposable.add(Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, curLatLng, false, true)) .loadAttractionsFromLocation(curLatLng, curLatLng, false, true))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -307,7 +304,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
Timber.d(throwable); Timber.d(throwable);
showErrorMessage(getString(R.string.error_fetching_nearby_places)); showErrorMessage(getString(R.string.error_fetching_nearby_places));
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
}); }));
} else if (locationChangeType } else if (locationChangeType
.equals(LOCATION_SLIGHTLY_CHANGED) && nearbyMapFragment != null) { .equals(LOCATION_SLIGHTLY_CHANGED) && nearbyMapFragment != null) {
@ -335,7 +332,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
populateForCurrentLocation = refreshForCurrentLocation; populateForCurrentLocation = refreshForCurrentLocation;
this.customLatLng = customLatLng; this.customLatLng = customLatLng;
placesDisposableCustom = Observable.fromCallable(() -> nearbyController compositeDisposable.add(Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, customLatLng, false, populateForCurrentLocation)) .loadAttractionsFromLocation(curLatLng, customLatLng, false, populateForCurrentLocation))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -343,7 +340,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
throwable -> { throwable -> {
Timber.d(throwable); Timber.d(throwable);
showErrorMessage(getString(R.string.error_fetching_nearby_places)); showErrorMessage(getString(R.string.error_fetching_nearby_places));
}); }));
if (nearbyMapFragment != null) { if (nearbyMapFragment != null) {
nearbyMapFragment.searchThisAreaButton.setVisibility(View.GONE); nearbyMapFragment.searchThisAreaButton.setVisibility(View.GONE);
@ -467,7 +464,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
|| curLatLng.getLongitude() < nearbyMapFragment.boundaryCoordinates[2].getLongitude() || curLatLng.getLongitude() < nearbyMapFragment.boundaryCoordinates[2].getLongitude()
|| curLatLng.getLongitude() > nearbyMapFragment.boundaryCoordinates[3].getLongitude())) { || curLatLng.getLongitude() > nearbyMapFragment.boundaryCoordinates[3].getLongitude())) {
// populate places // populate places
placesDisposable = Observable.fromCallable(() -> nearbyController compositeDisposable.add(Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, curLatLng, false, updateViaButton)) .loadAttractionsFromLocation(curLatLng, curLatLng, false, updateViaButton))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -476,7 +473,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
Timber.d(throwable); Timber.d(throwable);
showErrorMessage(getString(R.string.error_fetching_nearby_places)); showErrorMessage(getString(R.string.error_fetching_nearby_places));
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
}); }));
nearbyMapFragment.setBundleForUpdates(bundle); nearbyMapFragment.setBundleForUpdates(bundle);
nearbyMapFragment.updateMapSignificantlyForCurrentLocation(); nearbyMapFragment.updateMapSignificantlyForCurrentLocation();
updateListFragment(); updateListFragment();
@ -782,13 +779,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (placesDisposable != null) {
placesDisposable.dispose();
}
wikidataEditListener.setAuthenticationStateListener(null); wikidataEditListener.setAuthenticationStateListener(null);
if (placesDisposableCustom != null) {
placesDisposableCustom.dispose();
}
} }
@Override @Override

View file

@ -8,13 +8,14 @@ import javax.inject.Named;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerAppCompatActivity; import fr.free.nrw.commons.di.CommonsDaggerAppCompatActivity;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.kvstore.JsonKvStore; import io.reactivex.disposables.CompositeDisposable;
public abstract class BaseActivity extends CommonsDaggerAppCompatActivity { public abstract class BaseActivity extends CommonsDaggerAppCompatActivity {
@Inject @Inject
@Named("default_preferences") @Named("default_preferences")
public JsonKvStore defaultKvStore; public JsonKvStore defaultKvStore;
protected CompositeDisposable compositeDisposable = new CompositeDisposable();
protected boolean wasPreviouslyDarkTheme; protected boolean wasPreviouslyDarkTheme;
@Override @Override
@ -33,4 +34,10 @@ public abstract class BaseActivity extends CommonsDaggerAppCompatActivity {
super.onResume(); super.onResume();
} }
@Override
protected void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
}
} }

View file

@ -17,6 +17,7 @@ import androidx.exifinterface.media.ExifInterface;
import fr.free.nrw.commons.caching.CacheController; import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.mwapi.CategoryApi; import fr.free.nrw.commons.mwapi.CategoryApi;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -42,11 +43,16 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
private ExifInterface exifInterface; private ExifInterface exifInterface;
private boolean haveCheckedForOtherImages = false; private boolean haveCheckedForOtherImages = false;
private GPSExtractor tempImageObj; private GPSExtractor tempImageObj;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@Inject @Inject
FileProcessor() { FileProcessor() {
} }
public void cleanup() {
compositeDisposable.clear();
}
void initFileDetails(@NonNull String filePath, ContentResolver contentResolver) { void initFileDetails(@NonNull String filePath, ContentResolver contentResolver) {
this.filePath = filePath; this.filePath = filePath;
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
@ -138,7 +144,7 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories // If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
if (catListEmpty) { if (catListEmpty) {
apiCall.request(decimalCoords) compositeDisposable.add(apiCall.request(decimalCoords)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.subscribe( .subscribe(
@ -147,7 +153,7 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
Timber.e(throwable); Timber.e(throwable);
gpsCategoryModel.clear(); gpsCategoryModel.clear();
} }
); ));
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList); Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
} else { } else {
Timber.d("Cache found, setting categoryList in model to %s", displayCatList); Timber.d("Cache found, setting categoryList in model to %s", displayCatList);

View file

@ -33,16 +33,19 @@ public class ImageProcessingService {
private final MediaWikiApi mwApi; private final MediaWikiApi mwApi;
private final ReadFBMD readFBMD; private final ReadFBMD readFBMD;
private final EXIFReader EXIFReader; private final EXIFReader EXIFReader;
private final Context context;
@Inject @Inject
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper, public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
ImageUtilsWrapper imageUtilsWrapper, ImageUtilsWrapper imageUtilsWrapper,
MediaWikiApi mwApi, ReadFBMD readFBMD, EXIFReader EXIFReader) { MediaWikiApi mwApi, ReadFBMD readFBMD, EXIFReader EXIFReader,
Context context) {
this.fileUtilsWrapper = fileUtilsWrapper; this.fileUtilsWrapper = fileUtilsWrapper;
this.imageUtilsWrapper = imageUtilsWrapper; this.imageUtilsWrapper = imageUtilsWrapper;
this.mwApi = mwApi; this.mwApi = mwApi;
this.readFBMD = readFBMD; this.readFBMD = readFBMD;
this.EXIFReader = EXIFReader; this.EXIFReader = EXIFReader;
this.context = context;
} }
/** /**
@ -61,13 +64,13 @@ public class ImageProcessingService {
Timber.d("Checking the validity of image"); Timber.d("Checking the validity of image");
String filePath = uploadItem.getMediaUri().getPath(); String filePath = uploadItem.getMediaUri().getPath();
Uri contentUri=uploadItem.getContentUri(); Uri contentUri=uploadItem.getContentUri();
Context context=uploadItem.getContext();
Single<Integer> duplicateImage = checkDuplicateImage(filePath); Single<Integer> duplicateImage = checkDuplicateImage(filePath);
Single<Integer> wrongGeoLocation = checkImageGeoLocation(uploadItem.getPlace(), filePath); Single<Integer> wrongGeoLocation = checkImageGeoLocation(uploadItem.getPlace(), filePath);
Single<Integer> darkImage = checkDarkImage(filePath); Single<Integer> darkImage = checkDarkImage(filePath);
Single<Integer> itemTitle = checkTitle ? validateItemTitle(uploadItem) : Single.just(ImageUtils.IMAGE_OK); Single<Integer> itemTitle = checkTitle ? validateItemTitle(uploadItem) : Single.just(ImageUtils.IMAGE_OK);
Single<Integer> checkFBMD = checkFBMD(context,contentUri); Single<Integer> checkFBMD = checkFBMD(context,contentUri);
Single<Integer> checkEXIF = checkEXIF(filePath); Single<Integer> checkEXIF = checkEXIF(filePath);
Single<Integer> zipResult = Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle, Single<Integer> zipResult = Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle,
(duplicate, wrongGeo, dark, title) -> { (duplicate, wrongGeo, dark, title) -> {
Timber.d("Result for duplicate: %d, geo: %d, dark: %d, title: %d", duplicate, wrongGeo, dark, title); Timber.d("Result for duplicate: %d, geo: %d, dark: %d, title: %d", duplicate, wrongGeo, dark, title);

View file

@ -5,7 +5,6 @@ import android.annotation.SuppressLint;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
@ -67,7 +66,6 @@ import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -134,7 +132,6 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
private DescriptionsAdapter descriptionsAdapter; private DescriptionsAdapter descriptionsAdapter;
private RVRendererAdapter<CategoryItem> categoriesAdapter; private RVRendererAdapter<CategoryItem> categoriesAdapter;
private CompositeDisposable compositeDisposable;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
@ -145,7 +142,6 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
setContentView(R.layout.activity_upload); setContentView(R.layout.activity_upload);
ButterKnife.bind(this); ButterKnife.bind(this);
compositeDisposable = new CompositeDisposable();
configureLayout(); configureLayout();
configureTopCard(); configureTopCard();
@ -211,8 +207,6 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
@Override @Override
protected void onPause() { protected void onPause() {
presenter.removeView(); presenter.removeView();
compositeDisposable.dispose();
compositeDisposable = new CompositeDisposable();
super.onPause(); super.onPause();
} }
@ -571,7 +565,7 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void updateCategoryList(String filter) { private void updateCategoryList(String filter) {
List<String> imageTitleList = presenter.getImageTitleList(); List<String> imageTitleList = presenter.getImageTitleList();
Observable.fromIterable(categoriesModel.getSelectedCategories()) compositeDisposable.add(Observable.fromIterable(categoriesModel.getSelectedCategories())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> { .doOnSubscribe(disposable -> {
@ -602,7 +596,7 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
categoriesSearchContainer.setError("No categories found"); categoriesSearchContainer.setError("No categories found");
} }
} }
); ));
} }
private void receiveSharedItems() { private void receiveSharedItems() {

View file

@ -26,7 +26,7 @@ import fr.free.nrw.commons.utils.ImageUtils;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer; import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.BehaviorSubject; import io.reactivex.subjects.BehaviorSubject;
@ -45,6 +45,7 @@ public class UploadModel {
}; };
private final JsonKvStore store; private final JsonKvStore store;
private final List<String> licenses; private final List<String> licenses;
private final Context context;
private String license; private String license;
private final Map<String, String> licensesByName; private final Map<String, String> licensesByName;
private List<UploadItem> items = new ArrayList<>(); private List<UploadItem> items = new ArrayList<>();
@ -52,8 +53,7 @@ public class UploadModel {
private boolean bottomCardState = true; private boolean bottomCardState = true;
private boolean rightCardState = true; private boolean rightCardState = true;
private int currentStepIndex = 0; private int currentStepIndex = 0;
public static Context context; private CompositeDisposable compositeDisposable = new CompositeDisposable();
private Disposable badImageSubscription;
private SessionManager sessionManager; private SessionManager sessionManager;
private FileProcessor fileProcessor; private FileProcessor fileProcessor;
@ -77,6 +77,11 @@ public class UploadModel {
this.imageProcessingService = imageProcessingService; this.imageProcessingService = imageProcessingService;
} }
void cleanup() {
compositeDisposable.clear();
fileProcessor.cleanup();
}
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
Observable<UploadItem> preProcessImages(List<UploadableFile> uploadableFiles, Observable<UploadItem> preProcessImages(List<UploadableFile> uploadableFiles,
Place place, Place place,
@ -219,8 +224,7 @@ public class UploadModel {
} }
public void previous() { public void previous() {
if (badImageSubscription != null) cleanup();
badImageSubscription.dispose();
markCurrentUploadVisited(); markCurrentUploadVisited();
if (currentStepIndex > 0) { if (currentStepIndex > 0) {
currentStepIndex--; currentStepIndex--;
@ -305,16 +309,16 @@ public class UploadModel {
} }
void deletePicture() { void deletePicture() {
badImageSubscription.dispose(); cleanup();
updateItemState(); updateItemState();
} }
void subscribeBadPicture(Consumer<Integer> consumer, boolean checkTitle) { void subscribeBadPicture(Consumer<Integer> consumer, boolean checkTitle) {
if (isShowingItem()) { if (isShowingItem()) {
badImageSubscription = getImageQuality(getCurrentItem(), checkTitle) compositeDisposable.add(getImageQuality(getCurrentItem(), checkTitle)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(consumer, Timber::e); .subscribe(consumer, Timber::e));
} }
} }
@ -432,10 +436,6 @@ public class UploadModel {
public Uri getContentUri() { public Uri getContentUri() {
return originalContentUri; return originalContentUri;
} }
public Context getContext(){
return UploadModel.context;
}
} }
} }

View file

@ -21,6 +21,7 @@ import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.StringUtils; import fr.free.nrw.commons.utils.StringUtils;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -52,6 +53,7 @@ public class UploadPresenter {
private final UploadController uploadController; private final UploadController uploadController;
private final Context context; private final Context context;
private final JsonKvStore directKvStore; private final JsonKvStore directKvStore;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@Inject @Inject
UploadPresenter(UploadModel uploadModel, UploadPresenter(UploadModel uploadModel,
@ -77,12 +79,12 @@ public class UploadPresenter {
Observable<UploadItem> uploadItemObservable = uploadModel Observable<UploadItem> uploadItemObservable = uploadModel
.preProcessImages(media, place, source, similarImageInterface); .preProcessImages(media, place, source, similarImageInterface);
uploadItemObservable compositeDisposable.add(uploadItemObservable
.toList() .toList()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(uploadItems -> onImagesProcessed(uploadItems, place), .subscribe(uploadItems -> onImagesProcessed(uploadItems, place),
throwable -> Timber.e(throwable, "Error occurred in processing images")); throwable -> Timber.e(throwable, "Error occurred in processing images")));
} }
private void onImagesProcessed(List<UploadItem> uploadItems, Place place) { private void onImagesProcessed(List<UploadItem> uploadItems, Place place) {
@ -211,9 +213,9 @@ public class UploadPresenter {
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
void handleSubmit(CategoriesModel categoriesModel) { void handleSubmit(CategoriesModel categoriesModel) {
if (view.checkIfLoggedIn()) if (view.checkIfLoggedIn())
uploadModel.buildContributions(categoriesModel.getCategoryStringList()) compositeDisposable.add(uploadModel.buildContributions(categoriesModel.getCategoryStringList())
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.subscribe(uploadController::startUpload); .subscribe(uploadController::startUpload));
} }
/** /**
@ -286,6 +288,8 @@ public class UploadPresenter {
} }
void cleanup() { void cleanup() {
compositeDisposable.clear();
uploadModel.cleanup();
uploadController.cleanup(); uploadController.cleanup();
} }