mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-11-26 06:47:03 +03:00
#831 Better handling of error states while synchronizing capabilities and notes by using callbacks instead of LiveData (which is not really error aware)
This commit is contained in:
parent
e8e350e99c
commit
fa03e56883
4 changed files with 153 additions and 90 deletions
|
@ -0,0 +1,14 @@
|
|||
package it.niedermann.owncloud.notes.exception;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* This type of {@link Exception} occurs, when a user has an active internet connection but decided by intention not to use it.
|
||||
* Example: "Sync only on Wi-Fi" is set to <code>true</code>, Wi-Fi is not connected, mobile data is available
|
||||
*/
|
||||
public class IntendedOfflineException extends Exception {
|
||||
|
||||
public IntendedOfflineException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -58,6 +58,7 @@ import it.niedermann.owncloud.notes.edit.EditNoteActivity;
|
|||
import it.niedermann.owncloud.notes.edit.category.CategoryDialogFragment;
|
||||
import it.niedermann.owncloud.notes.edit.category.CategoryViewModel;
|
||||
import it.niedermann.owncloud.notes.exception.ExceptionDialogFragment;
|
||||
import it.niedermann.owncloud.notes.exception.IntendedOfflineException;
|
||||
import it.niedermann.owncloud.notes.importaccount.ImportAccountActivity;
|
||||
import it.niedermann.owncloud.notes.main.items.ItemAdapter;
|
||||
import it.niedermann.owncloud.notes.main.items.grid.GridItemDecoration;
|
||||
|
@ -74,6 +75,7 @@ import it.niedermann.owncloud.notes.persistence.entity.Account;
|
|||
import it.niedermann.owncloud.notes.persistence.entity.Note;
|
||||
import it.niedermann.owncloud.notes.shared.model.Capabilities;
|
||||
import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
|
||||
import it.niedermann.owncloud.notes.shared.model.IResponseCallback;
|
||||
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
|
||||
import it.niedermann.owncloud.notes.shared.model.NoteClickListener;
|
||||
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
|
||||
|
@ -265,11 +267,21 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
|
|||
.apply(RequestOptions.circleCropTransform())
|
||||
.into(activityBinding.launchAccountSwitcher);
|
||||
|
||||
final LiveData<Boolean> syncLiveData = mainViewModel.synchronize();
|
||||
syncLiveData.observe(this, (syncSuccess) -> {
|
||||
syncLiveData.removeObservers(this);
|
||||
if (!syncSuccess) {
|
||||
BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show();
|
||||
mainViewModel.synchronizeNotes(nextAccount, new IResponseCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Log.d(TAG, "Successfully synchronized notes for " + nextAccount.getAccountName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable t) {
|
||||
runOnUiThread(() -> {
|
||||
if (t.getClass() == IntendedOfflineException.class || t instanceof IntendedOfflineException) {
|
||||
Log.i(TAG, "Capabilities and notes not updated because " + nextAccount.getAccountName() + " is offline by intention.");
|
||||
} else {
|
||||
BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
fabCreate.show();
|
||||
|
@ -294,12 +306,20 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
|
|||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
final LiveData<Boolean> syncLiveData = mainViewModel.synchronize();
|
||||
syncLiveData.observe(this, (syncSuccess) -> {
|
||||
syncLiveData.removeObservers(this);
|
||||
if (!syncSuccess) {
|
||||
BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
final LiveData<Account> accountLiveData = mainViewModel.getCurrentAccount();
|
||||
accountLiveData.observe(this, (currentAccount) -> {
|
||||
accountLiveData.removeObservers(this);
|
||||
mainViewModel.synchronizeNotes(currentAccount, new IResponseCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Log.d(TAG, "Successfully synchronized notes for " + currentAccount.getAccountName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
});
|
||||
super.onResume();
|
||||
}
|
||||
|
@ -404,13 +424,28 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
|
|||
new Thread(() -> {
|
||||
Log.i(TAG, "Clearing Glide disk cache");
|
||||
Glide.get(getApplicationContext()).clearDiskCache();
|
||||
}).start();
|
||||
final LiveData<Boolean> syncLiveData = mainViewModel.performFullSynchronizationForCurrentAccount();
|
||||
final Observer<Boolean> syncObserver = syncSuccess -> {
|
||||
if (!syncSuccess) {
|
||||
BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}, "CLEAR_GLIDE_CACHE").start();
|
||||
final LiveData<Account> syncLiveData = mainViewModel.getCurrentAccount();
|
||||
final Observer<Account> syncObserver = currentAccount -> {
|
||||
syncLiveData.removeObservers(this);
|
||||
mainViewModel.synchronizeCapabilitiesAndNotes(currentAccount, new IResponseCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Log.d(TAG, "Successfully synchronized capabilities and notes for " + currentAccount.getAccountName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable t) {
|
||||
runOnUiThread(() -> {
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
if (t.getClass() == IntendedOfflineException.class || t instanceof IntendedOfflineException) {
|
||||
Log.i(TAG, "Capabilities and notes not updated because " + currentAccount.getAccountName() + " is offline by intention.");
|
||||
} else {
|
||||
BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
syncLiveData.observe(this, syncObserver);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package it.niedermann.owncloud.notes.main;
|
||||
|
||||
import android.accounts.NetworkErrorException;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
@ -29,6 +30,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import it.niedermann.owncloud.notes.R;
|
||||
import it.niedermann.owncloud.notes.branding.BrandingUtil;
|
||||
import it.niedermann.owncloud.notes.exception.IntendedOfflineException;
|
||||
import it.niedermann.owncloud.notes.main.navigation.NavigationAdapter;
|
||||
import it.niedermann.owncloud.notes.main.navigation.NavigationItem;
|
||||
import it.niedermann.owncloud.notes.persistence.CapabilitiesClient;
|
||||
|
@ -40,6 +42,7 @@ import it.niedermann.owncloud.notes.persistence.entity.Note;
|
|||
import it.niedermann.owncloud.notes.persistence.entity.SingleNoteWidgetData;
|
||||
import it.niedermann.owncloud.notes.shared.model.Capabilities;
|
||||
import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
|
||||
import it.niedermann.owncloud.notes.shared.model.IResponseCallback;
|
||||
import it.niedermann.owncloud.notes.shared.model.Item;
|
||||
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
|
||||
|
||||
|
@ -366,34 +369,85 @@ public class MainViewModel extends AndroidViewModel {
|
|||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code>, if a synchronization could successfully be triggered, <code>false</code> if not.
|
||||
*/
|
||||
public LiveData<Boolean> synchronize() {
|
||||
return switchMap(getCurrentAccount(), currentAccount -> {
|
||||
if (currentAccount == null) {
|
||||
return new MutableLiveData<>(false);
|
||||
} else {
|
||||
Log.v(TAG, "[synchronize] - currentAccount: " + currentAccount.getAccountName());
|
||||
NotesServerSyncHelper syncHelper = db.getNoteServerSyncHelper();
|
||||
if (!syncHelper.isSyncPossible()) {
|
||||
syncHelper.updateNetworkStatus();
|
||||
}
|
||||
if (syncHelper.isSyncPossible()) {
|
||||
syncHelper.scheduleSync(currentAccount, false);
|
||||
return new MutableLiveData<>(true);
|
||||
} else { // Sync is not possible
|
||||
if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) {
|
||||
Log.d(TAG, "Network is connected, but sync is not possible");
|
||||
} else {
|
||||
Log.d(TAG, "Sync is not possible, because network is not connected");
|
||||
}
|
||||
}
|
||||
return new MutableLiveData<>(false);
|
||||
public void synchronizeCapabilitiesAndNotes(@NonNull Account localAccount, @NonNull IResponseCallback callback) {
|
||||
Log.i(TAG, "[synchronizeCapabilitiesAndNotes] Synchronize capabilities for " + localAccount.getAccountName());
|
||||
synchronizeCapabilities(localAccount, new IResponseCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Log.i(TAG, "[synchronizeCapabilitiesAndNotes] Synchronize notes for " + localAccount.getAccountName());
|
||||
synchronizeNotes(localAccount, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable t) {
|
||||
callback.onError(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the network status if necessary and pulls the latest {@link Capabilities} of the given {@param localAccount}
|
||||
*/
|
||||
public void synchronizeCapabilities(@NonNull Account localAccount, @NonNull IResponseCallback callback) {
|
||||
new Thread(() -> {
|
||||
final NotesServerSyncHelper syncHelper = db.getNoteServerSyncHelper();
|
||||
if (!syncHelper.isSyncPossible()) {
|
||||
syncHelper.updateNetworkStatus();
|
||||
}
|
||||
if (syncHelper.isSyncPossible()) {
|
||||
try {
|
||||
final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplication(), AccountImporter.getSingleSignOnAccount(getApplication(), localAccount.getAccountName()), localAccount.getCapabilitiesETag());
|
||||
db.getAccountDao().updateCapabilitiesETag(localAccount.getId(), capabilities.getETag());
|
||||
db.getAccountDao().updateBrand(localAccount.getId(), capabilities.getColor(), capabilities.getTextColor());
|
||||
localAccount.setColor(capabilities.getColor());
|
||||
localAccount.setTextColor(capabilities.getTextColor());
|
||||
BrandingUtil.saveBrandColors(getApplication(), localAccount.getColor(), localAccount.getTextColor());
|
||||
db.updateApiVersion(localAccount.getId(), capabilities.getApiVersion());
|
||||
callback.onSuccess();
|
||||
} catch (NextcloudFilesAppAccountNotFoundException e) {
|
||||
db.getAccountDao().deleteAccount(localAccount);
|
||||
callback.onError(e);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
Log.i(TAG, "[synchronizeCapabilities] Capabilities not modified.");
|
||||
callback.onSuccess();
|
||||
} else {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) {
|
||||
callback.onError(new IntendedOfflineException("Network is connected, but sync is not possible."));
|
||||
} else {
|
||||
callback.onError(new NetworkErrorException("Sync is not possible, because network is not connected."));
|
||||
}
|
||||
}
|
||||
}, "SYNC_CAPABILITIES").start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the network status if necessary and pulls the latest notes of the given {@param localAccount}
|
||||
*/
|
||||
public void synchronizeNotes(@NonNull Account currentAccount, @NonNull IResponseCallback callback) {
|
||||
new Thread(() -> {
|
||||
Log.v(TAG, "[synchronize] - currentAccount: " + currentAccount.getAccountName());
|
||||
final NotesServerSyncHelper syncHelper = db.getNoteServerSyncHelper();
|
||||
if (!syncHelper.isSyncPossible()) {
|
||||
syncHelper.updateNetworkStatus();
|
||||
}
|
||||
if (syncHelper.isSyncPossible()) {
|
||||
syncHelper.scheduleSync(currentAccount, false);
|
||||
callback.onSuccess();
|
||||
} else { // Sync is not possible
|
||||
if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) {
|
||||
callback.onError(new IntendedOfflineException("Network is connected, but sync is not possible."));
|
||||
} else {
|
||||
callback.onError(new NetworkErrorException("Sync is not possible, because network is not connected."));
|
||||
}
|
||||
}
|
||||
}, "SYNC_NOTES").start();
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getSyncStatus() {
|
||||
return db.getNoteServerSyncHelper().getSyncStatus();
|
||||
}
|
||||
|
@ -406,55 +460,6 @@ public class MainViewModel extends AndroidViewModel {
|
|||
return map(db.getAccountDao().countAccounts$(), (counter) -> counter != null && counter > 1);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> performFullSynchronizationForCurrentAccount() {
|
||||
final MutableLiveData<Boolean> insufficientInformation = new MutableLiveData<>();
|
||||
return switchMap(getCurrentAccount(), localAccount -> {
|
||||
Log.v(TAG, "[performFullSynchronizationForCurrentAccount] - currentAccount: " + localAccount);
|
||||
if (localAccount == null) {
|
||||
return insufficientInformation;
|
||||
} else {
|
||||
Log.i(TAG, "[performFullSynchronizationForCurrentAccount] Refreshing capabilities for " + localAccount.getAccountName());
|
||||
final MutableLiveData<Boolean> syncCapabilitiesLiveData = new MutableLiveData<>();
|
||||
new Thread(() -> {
|
||||
final Capabilities capabilities;
|
||||
try {
|
||||
capabilities = CapabilitiesClient.getCapabilities(getApplication(), AccountImporter.getSingleSignOnAccount(getApplication(), localAccount.getAccountName()), localAccount.getCapabilitiesETag());
|
||||
db.getAccountDao().updateCapabilitiesETag(localAccount.getId(), capabilities.getETag());
|
||||
db.getAccountDao().updateBrand(localAccount.getId(), capabilities.getColor(), capabilities.getTextColor());
|
||||
localAccount.setColor(capabilities.getColor());
|
||||
localAccount.setTextColor(capabilities.getTextColor());
|
||||
BrandingUtil.saveBrandColors(getApplication(), localAccount.getColor(), localAccount.getTextColor());
|
||||
db.updateApiVersion(localAccount.getId(), capabilities.getApiVersion());
|
||||
Log.i(TAG, capabilities.toString());
|
||||
syncCapabilitiesLiveData.postValue(true);
|
||||
} catch (NextcloudFilesAppAccountNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
db.getAccountDao().deleteAccount(localAccount);
|
||||
syncCapabilitiesLiveData.postValue(false);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
Log.i(TAG, "[performFullSynchronizationForCurrentAccount] Capabilities not modified.");
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// Capabilities couldn't be update correctly, we can still try to sync the notes list.
|
||||
syncCapabilitiesLiveData.postValue(true);
|
||||
}
|
||||
|
||||
}).start();
|
||||
return switchMap(syncCapabilitiesLiveData, capabilitiesSyncedSuccessfully -> {
|
||||
if (Boolean.TRUE.equals(capabilitiesSyncedSuccessfully)) {
|
||||
Log.v(TAG, "[performFullSynchronizationForCurrentAccount] Capabilities refreshed successfully - synchronize notes for " + localAccount.getAccountName());
|
||||
return synchronize();
|
||||
} else {
|
||||
Log.w(TAG, "[performFullSynchronizationForCurrentAccount] Capabilities could not be refreshed correctly - end synchronization process here.");
|
||||
return new MutableLiveData<>(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public Account getLocalAccountByAccountName(String accountName) {
|
||||
return db.getAccountDao().getAccountByName(accountName);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package it.niedermann.owncloud.notes.shared.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface IResponseCallback {
|
||||
void onSuccess();
|
||||
|
||||
void onError(@NonNull Throwable t);
|
||||
}
|
Loading…
Reference in a new issue