Fixed multiple observers

This commit is contained in:
M66B 2018-12-05 11:50:07 +01:00
parent 3aa89cae97
commit c4733427ae
6 changed files with 231 additions and 173 deletions

View File

@ -96,6 +96,7 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.paging.AsyncPagedListDiffer;
@ -188,6 +189,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private Group grpAttachments;
private Group grpExpanded;
private LiveData<List<EntityAttachment>> liveAttachments = null;
private Observer<List<EntityAttachment>> observerAttachments = null;
ViewHolder(View itemView) {
super(itemView);
@ -400,25 +404,16 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}
if (debug) {
db.operation().getOperationsByMessage(message.id).removeObservers(owner);
db.operation().getOperationsByMessage(message.id).observe(owner, new Observer<List<EntityOperation>>() {
@Override
public void onChanged(List<EntityOperation> operations) {
String text = message.error +
"\n" + message.uid + "/" + message.id + " " + df.format(new Date(message.received)) +
"\n" + (message.ui_hide ? "HIDDEN " : "") +
"seen=" + message.seen + "/" + message.ui_seen + "/" + message.unseen +
" found=" + message.ui_found +
"\n" + message.msgid +
"\n" + message.thread;
if (operations != null)
for (EntityOperation op : operations)
text += "\n" + op.id + ":" + op.name + " " + df.format(new Date(op.created));
String text = message.error +
"\n" + message.uid + "/" + message.id + " " + df.format(new Date(message.received)) +
"\n" + (message.ui_hide ? "HIDDEN " : "") +
"seen=" + message.seen + "/" + message.ui_seen + "/" + message.unseen +
" found=" + message.ui_found +
"\n" + message.msgid +
"\n" + message.thread;
tvError.setText(text);
tvError.setVisibility(View.VISIBLE);
}
});
tvError.setText(text);
tvError.setVisibility(View.VISIBLE);
} else {
tvError.setText(message.error);
tvError.setVisibility(message.error == null ? View.GONE : View.VISIBLE);
@ -451,9 +446,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
grpAttachments.setVisibility(message.attachments > 0 && show_expanded ? View.VISIBLE : View.GONE);
grpExpanded.setVisibility(viewType == ViewType.THREAD && show_expanded ? View.VISIBLE : View.GONE);
db.folder().liveSystemFolders(message.account).removeObservers(owner);
db.attachment().liveAttachments(message.id).removeObservers(owner);
bnvActions.setTag(null);
if (show_expanded) {
@ -488,9 +480,18 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
bodyTask.load(context, owner, args);
}
db.folder().liveSystemFolders(message.account).observe(owner, new Observer<List<EntityFolder>>() {
Bundle sargs = new Bundle();
sargs.putLong("account", message.account);
new SimpleTask<List<EntityFolder>>() {
@Override
public void onChanged(@Nullable List<EntityFolder> folders) {
protected List<EntityFolder> onLoad(Context context, Bundle args) {
long account = args.getLong("account");
return DB.getInstance(context).folder().getSystemFolders(account);
}
@Override
protected void onLoaded(Bundle args, List<EntityFolder> folders) {
boolean hasJunk = false;
boolean hasTrash = false;
boolean hasArchive = false;
@ -524,30 +525,43 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
bnvActions.setVisibility(View.VISIBLE);
vSeparatorBody.setVisibility(View.GONE);
}
});
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.load(context, owner, sargs);
// Observe attachments
db.attachment().liveAttachments(message.id).observe(owner,
new Observer<List<EntityAttachment>>() {
@Override
public void onChanged(@Nullable List<EntityAttachment> attachments) {
if (attachments == null)
attachments = new ArrayList<>();
observerAttachments = new Observer<List<EntityAttachment>>() {
@Override
public void onChanged(@Nullable List<EntityAttachment> attachments) {
if (attachments == null)
attachments = new ArrayList<>();
adapter.set(attachments);
adapter.set(attachments);
if (message.content) {
Bundle args = new Bundle();
args.putSerializable("message", message);
bodyTask.load(context, owner, args);
}
}
});
if (message.content) {
Bundle args = new Bundle();
args.putSerializable("message", message);
bodyTask.load(context, owner, args);
}
}
};
liveAttachments = db.attachment().liveAttachments(message.id);
liveAttachments.observe(owner, observerAttachments);
}
itemView.setActivated(selectionTracker != null && selectionTracker.isSelected(message.id));
}
void unbind() {
if (liveAttachments != null) {
liveAttachments.removeObserver(observerAttachments);
liveAttachments = null;
}
}
@Override
public void onClick(View view) {
int pos = getAdapterPosition();
@ -1040,10 +1054,14 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}
private void onAnswer(final ActionData data) {
final DB db = DB.getInstance(context);
db.answer().liveAnswers().observe(owner, new Observer<List<EntityAnswer>>() {
new SimpleTask<List<EntityAnswer>>() {
@Override
public void onChanged(List<EntityAnswer> answers) {
protected List<EntityAnswer> onLoad(Context context, Bundle args) {
return DB.getInstance(context).answer().getAnswers();
}
@Override
protected void onLoaded(Bundle args, List<EntityAnswer> answers) {
if (answers == null || answers.size() == 0) {
Snackbar snackbar = Snackbar.make(
itemView,
@ -1094,10 +1112,13 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
popupMenu.show();
}
db.answer().liveAnswers().removeObservers(owner);
}
});
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.load(context, owner, new Bundle());
}
private void onUnseen(final ActionData data) {
@ -1616,6 +1637,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unbind();
holder.unwire();
TupleMessageEx message = differ.getItem(position);
@ -1627,6 +1649,11 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}
}
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
holder.unbind();
}
void setSelectionTracker(SelectionTracker<Long> selectionTracker) {
this.selectionTracker = selectionTracker;
}

View File

@ -97,6 +97,11 @@ public interface DaoFolder {
@Query("SELECT * FROM folder ORDER BY account, name")
List<EntityFolder> getFolders();
@Query("SELECT * FROM folder" +
" WHERE folder.account = :account" +
" AND type <> '" + EntityFolder.USER + "'")
List<EntityFolder> getSystemFolders(long account);
@Query("SELECT * FROM folder WHERE id = :id")
EntityFolder getFolder(Long id);

View File

@ -105,6 +105,7 @@ import androidx.core.content.ContextCompat;
import androidx.cursoradapter.widget.SimpleCursorAdapter;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -1347,8 +1348,9 @@ public class FragmentCompose extends FragmentEx {
final DB db = DB.getInstance(getContext());
db.account().liveAccounts(true).removeObservers(getViewLifecycleOwner());
db.account().liveAccounts(true).observe(getViewLifecycleOwner(), new Observer<List<EntityAccount>>() {
private LiveData<List<EntityIdentity>> liveIdentities = null;
@Override
public void onChanged(List<EntityAccount> accounts) {
if (accounts == null)
@ -1374,8 +1376,12 @@ public class FragmentCompose extends FragmentEx {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
EntityAccount account = (EntityAccount) parent.getAdapter().getItem(position);
db.identity().liveIdentities(account.id, true).removeObservers(getViewLifecycleOwner());
db.identity().liveIdentities(account.id, true).observe(getViewLifecycleOwner(), new Observer<List<EntityIdentity>>() {
if (liveIdentities == null)
liveIdentities = db.identity().liveIdentities(account.id, true);
else
liveIdentities.removeObservers(getViewLifecycleOwner());
liveIdentities.observe(getViewLifecycleOwner(), new Observer<List<EntityIdentity>>() {
@Override
public void onChanged(@Nullable List<EntityIdentity> identities) {
if (identities == null)
@ -1449,7 +1455,6 @@ public class FragmentCompose extends FragmentEx {
}
});
db.attachment().liveAttachments(result.draft.id).removeObservers(getViewLifecycleOwner());
db.attachment().liveAttachments(result.draft.id).observe(getViewLifecycleOwner(),
new Observer<List<EntityAttachment>>() {
@Override
@ -1462,7 +1467,6 @@ public class FragmentCompose extends FragmentEx {
}
});
db.message().liveMessage(result.draft.id).removeObservers(getViewLifecycleOwner());
db.message().liveMessage(result.draft.id).observe(getViewLifecycleOwner(), new Observer<EntityMessage>() {
@Override
public void onChanged(final EntityMessage draft) {

View File

@ -202,6 +202,7 @@ public class FragmentMessages extends FragmentEx {
Bundle args = new Bundle();
args.putLong("account", account);
args.putLong("folder", folder);
new SimpleTask<Void>() {
@Override
protected Void onLoad(Context context, Bundle args) {

View File

@ -124,6 +124,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleService;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
@ -1075,94 +1076,111 @@ public class ServiceSynchronize extends LifecycleService {
// Observe operations
Handler handler = new Handler(getMainLooper()) {
private List<Long> handling = new ArrayList<>();
private final PowerManager.WakeLock wlFolder = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":folder." + folder.id);
private final ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
private LiveData<List<EntityOperation>> liveOperations;
@Override
public void handleMessage(android.os.Message msg) {
Log.i(Helper.TAG, folder.name + " observe=" + msg.what);
if (msg.what == 0)
db.operation().liveOperations(folder.id).removeObservers(ServiceSynchronize.this);
else
db.operation().liveOperations(folder.id).observe(ServiceSynchronize.this, new Observer<List<EntityOperation>>() {
@Override
public void onChanged(List<EntityOperation> operations) {
boolean process = false;
List<Long> current = new ArrayList<>();
for (EntityOperation op : operations) {
if (!handling.contains(op.id))
process = true;
current.add(op.id);
}
handling = current;
Log.i(Helper.TAG, account.name + "/" + folder.name + " observe=" + msg.what);
try {
if (msg.what == 0)
liveOperations.removeObserver(observer);
else {
liveOperations = db.operation().liveOperations(folder.id);
liveOperations.observe(ServiceSynchronize.this, observer);
}
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
}
}
if (handling.size() > 0 && process) {
Log.i(Helper.TAG, folder.name + " operations=" + operations.size());
executor.submit(new Runnable() {
@Override
public void run() {
try {
wlFolder.acquire();
Log.i(Helper.TAG, folder.name + " process");
private Observer<List<EntityOperation>> observer = new Observer<List<EntityOperation>>() {
private List<Long> handling = new ArrayList<>();
private final ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
private final PowerManager.WakeLock wlFolder = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":folder." + folder.id);
// Get folder
IMAPFolder ifolder = null;
for (EntityFolder f : folders.keySet())
if (f.id.equals(folder.id)) {
ifolder = folders.get(f); // null when polling
break;
}
@Override
public void onChanged(List<EntityOperation> operations) {
boolean process = false;
List<Long> current = new ArrayList<>();
for (EntityOperation op : operations) {
if (!handling.contains(op.id))
process = true;
current.add(op.id);
}
handling = current;
final boolean shouldClose = (ifolder == null);
if (handling.size() > 0 && process) {
Log.i(Helper.TAG, folder.name + " operations=" + operations.size());
executor.submit(new Runnable() {
@Override
public void run() {
try {
wlFolder.acquire();
Log.i(Helper.TAG, folder.name + " process");
try {
Log.i(Helper.TAG, folder.name + " run " + (shouldClose ? "offline" : "online"));
// Get folder
IMAPFolder ifolder = null;
for (EntityFolder f : folders.keySet())
if (f.id.equals(folder.id)) {
ifolder = folders.get(f); // null when polling
break;
}
if (ifolder == null) {
// Prevent unnecessary folder connections
if (db.operation().getOperationCount(folder.id, null) == 0)
return;
final boolean shouldClose = (ifolder == null);
db.folder().setFolderState(folder.id, "connecting");
try {
Log.i(Helper.TAG, folder.name + " run " + (shouldClose ? "offline" : "online"));
ifolder = (IMAPFolder) istore.getFolder(folder.name);
ifolder.open(Folder.READ_WRITE);
if (ifolder == null) {
// Prevent unnecessary folder connections
if (db.operation().getOperationCount(folder.id, null) == 0)
return;
db.folder().setFolderState(folder.id, "connected");
db.folder().setFolderError(folder.id, null);
}
db.folder().setFolderState(folder.id, "connecting");
processOperations(account, folder, isession, istore, ifolder, state);
ifolder = (IMAPFolder) istore.getFolder(folder.name);
ifolder.open(Folder.READ_WRITE);
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
if (shouldClose) {
if (ifolder != null && ifolder.isOpen()) {
db.folder().setFolderState(folder.id, "closing");
try {
ifolder.close(false);
} catch (MessagingException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
}
db.folder().setFolderState(folder.id, null);
db.folder().setFolderState(folder.id, "connected");
db.folder().setFolderError(folder.id, null);
}
processOperations(account, folder, isession, istore, ifolder, state);
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
if (shouldClose) {
if (ifolder != null && ifolder.isOpen()) {
db.folder().setFolderState(folder.id, "closing");
try {
ifolder.close(false);
} catch (MessagingException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
}
} finally {
wlFolder.release();
db.folder().setFolderState(folder.id, null);
}
}
});
} finally {
wlFolder.release();
}
}
}
});
}
});
}
}
@Override
public boolean equals(@Nullable Object obj) {
boolean eq = super.equals(obj);
Log.i(Helper.TAG, account.name + "/" + folder.name + " equal=" + eq + " observer=" + observer + " other=" + obj);
return eq;
}
};
};
// Start watching for operations
@ -2423,60 +2441,63 @@ public class ServiceSynchronize extends LifecycleService {
db.folder().setFolderError(outbox.id, null);
handler = new Handler(Looper.getMainLooper()) {
private LiveData<List<EntityOperation>> liveOperations;
@Override
public void handleMessage(android.os.Message msg) {
Log.i(Helper.TAG, outbox.name + " observe=" + msg.what);
if (msg.what == 0)
db.operation().liveOperations(outbox.id).removeObservers(ServiceSynchronize.this);
liveOperations.removeObserver(observer);
else {
db.operation().liveOperations(outbox.id).observe(ServiceSynchronize.this, new Observer<List<EntityOperation>>() {
private List<Long> handling = new ArrayList<>();
private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
@Override
public void onChanged(List<EntityOperation> operations) {
boolean process = false;
List<Long> current = new ArrayList<>();
for (EntityOperation op : operations) {
if (!handling.contains(op.id))
process = true;
current.add(op.id);
}
handling = current;
if (handling.size() > 0 && process) {
Log.i(Helper.TAG, outbox.name + " operations=" + operations.size());
executor.submit(new Runnable() {
PowerManager pm = getSystemService(PowerManager.class);
PowerManager.WakeLock wl = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":outbox");
@Override
public void run() {
try {
wl.acquire();
Log.i(Helper.TAG, outbox.name + " process");
db.folder().setFolderSyncState(outbox.id, "syncing");
processOperations(null, outbox, null, null, null, state);
db.folder().setFolderError(outbox.id, null);
} catch (Throwable ex) {
Log.e(Helper.TAG, outbox.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(null, outbox, ex);
db.folder().setFolderError(outbox.id, Helper.formatThrowable(ex));
} finally {
db.folder().setFolderSyncState(outbox.id, null);
wl.release();
EntityLog.log(ServiceSynchronize.this, "Outbox wake lock=" + wl.isHeld());
}
}
});
}
}
});
liveOperations = db.operation().liveOperations(outbox.id);
liveOperations.observe(ServiceSynchronize.this, observer);
}
}
private Observer<List<EntityOperation>> observer = new Observer<List<EntityOperation>>() {
private List<Long> handling = new ArrayList<>();
private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
PowerManager pm = getSystemService(PowerManager.class);
PowerManager.WakeLock wl = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":outbox");
@Override
public void onChanged(List<EntityOperation> operations) {
boolean process = false;
List<Long> current = new ArrayList<>();
for (EntityOperation op : operations) {
if (!handling.contains(op.id))
process = true;
current.add(op.id);
}
handling = current;
if (handling.size() > 0 && process) {
Log.i(Helper.TAG, outbox.name + " operations=" + operations.size());
executor.submit(new Runnable() {
@Override
public void run() {
try {
wl.acquire();
Log.i(Helper.TAG, outbox.name + " process");
db.folder().setFolderSyncState(outbox.id, "syncing");
processOperations(null, outbox, null, null, null, state);
db.folder().setFolderError(outbox.id, null);
} catch (Throwable ex) {
Log.e(Helper.TAG, outbox.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(null, outbox, ex);
db.folder().setFolderError(outbox.id, Helper.formatThrowable(ex));
} finally {
db.folder().setFolderSyncState(outbox.id, null);
wl.release();
EntityLog.log(ServiceSynchronize.this, "Outbox wake lock=" + wl.isHeld());
}
}
});
}
}
};
};
handler.sendEmptyMessage(1);
db.folder().setFolderState(outbox.id, "connected");

View File

@ -31,11 +31,13 @@ import android.util.Log;
import java.util.List;
import androidx.lifecycle.LifecycleService;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
@TargetApi(Build.VERSION_CODES.N)
public class ServiceTileUnseen extends TileService {
LifecycleService owner = new LifecycleService();
private LifecycleService owner = new LifecycleService();
private LiveData<List<TupleMessageEx>> liveMessages;
@Override
public void onCreate() {
@ -63,9 +65,8 @@ public class ServiceTileUnseen extends TileService {
public void onStartListening() {
Log.i(Helper.TAG, "Start tile unseen");
DB db = DB.getInstance(this);
db.message().liveUnseenUnified().observe(owner, new Observer<List<TupleMessageEx>>() {
liveMessages = DB.getInstance(this).message().liveUnseenUnified();
liveMessages.observe(owner, new Observer<List<TupleMessageEx>>() {
@Override
public void onChanged(List<TupleMessageEx> messages) {
Log.i(Helper.TAG, "Update tile unseen=" + messages.size());
@ -85,9 +86,8 @@ public class ServiceTileUnseen extends TileService {
public void onStopListening() {
Log.i(Helper.TAG, "Stop tile unseen");
DB db = DB.getInstance(this);
db.message().liveUnseenUnified().removeObservers(owner);
if (liveMessages != null)
liveMessages.removeObservers(owner);
}
public void onClick() {