Avoid race conditions when updating the UI from events.

This commit is contained in:
akwizgran
2016-10-17 10:54:00 +01:00
parent 50a70f7649
commit 2140a290e4
14 changed files with 239 additions and 93 deletions

View File

@@ -32,6 +32,7 @@ import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -51,6 +52,7 @@ public class BlogFragment extends BaseFragment implements
OnBlogPostAddedListener { OnBlogPostAddedListener {
public final static String TAG = BlogFragment.class.getName(); public final static String TAG = BlogFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Inject @Inject
BlogController blogController; BlogController blogController;
@@ -207,6 +209,7 @@ public class BlogFragment extends BaseFragment implements
listener) { listener) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
adapter.incrementRevision();
adapter.add(post); adapter.add(post);
if (local) { if (local) {
list.scrollToPosition(0); list.scrollToPosition(0);
@@ -228,16 +231,23 @@ public class BlogFragment extends BaseFragment implements
} }
void loadBlogPosts(final boolean reload) { void loadBlogPosts(final boolean reload) {
final int revision = adapter.getRevision();
blogController.loadBlogPosts( blogController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
listener) { listener) {
@Override @Override
public void onResultUi(Collection<BlogPostItem> posts) { public void onResultUi(Collection<BlogPostItem> posts) {
if (posts.isEmpty()) { if (revision == adapter.getRevision()) {
list.showData(); adapter.incrementRevision();
if (posts.isEmpty()) {
list.showData();
} else {
adapter.addAll(posts);
if (reload) list.scrollToPosition(0);
}
} else { } else {
adapter.addAll(posts); LOG.info("Concurrent update, reloading");
if (reload) list.scrollToPosition(0); loadBlogPosts(reload);
} }
} }

View File

@@ -27,6 +27,7 @@ import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -41,6 +42,7 @@ public class FeedFragment extends BaseFragment implements
OnBlogPostClickListener, OnBlogPostAddedListener { OnBlogPostClickListener, OnBlogPostAddedListener {
public final static String TAG = FeedFragment.class.getName(); public final static String TAG = FeedFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Inject @Inject
FeedController feedController; FeedController feedController;
@@ -129,14 +131,21 @@ public class FeedFragment extends BaseFragment implements
} }
private void loadBlogPosts(final boolean clear) { private void loadBlogPosts(final boolean clear) {
final int revision = adapter.getRevision();
feedController.loadBlogPosts( feedController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
listener) { listener) {
@Override @Override
public void onResultUi(Collection<BlogPostItem> posts) { public void onResultUi(Collection<BlogPostItem> posts) {
if (clear) adapter.setItems(posts); if (revision == adapter.getRevision()) {
else adapter.addAll(posts); adapter.incrementRevision();
if (posts.isEmpty()) list.showData(); if (clear) adapter.setItems(posts);
else adapter.addAll(posts);
if (posts.isEmpty()) list.showData();
} else {
LOG.info("Concurrent update, reloading");
loadBlogPosts(clear);
}
} }
@Override @Override
@@ -193,6 +202,7 @@ public class FeedFragment extends BaseFragment implements
listener) { listener) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
adapter.incrementRevision();
adapter.add(post); adapter.add(post);
if (local) { if (local) {
showSnackBar(R.string.blogs_blog_post_created); showSnackBar(R.string.blogs_blog_post_created);

View File

@@ -125,27 +125,43 @@ public class RssFeedManageActivity extends BriarActivity
} }
private void loadFeeds() { private void loadFeeds() {
final int revision = adapter.getRevision();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
addFeeds(feedManager.getFeeds()); displayFeeds(revision, feedManager.getFeeds());
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
list.setEmptyText(R.string.blogs_rss_feeds_manage_error); onLoadError();
list.showData();
} }
} }
}); });
} }
private void addFeeds(final List<Feed> feeds) { private void displayFeeds(final int revision, final List<Feed> feeds) {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (feeds.isEmpty()) list.showData(); if (revision == adapter.getRevision()) {
else adapter.addAll(feeds); adapter.incrementRevision();
if (feeds.isEmpty()) list.showData();
else adapter.addAll(feeds);
} else {
LOG.info("Concurrent update, reloading");
loadFeeds();
}
}
});
}
private void onLoadError() {
runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
list.setEmptyText(R.string.blogs_rss_feeds_manage_error);
list.showData();
} }
}); });
} }
@@ -154,6 +170,7 @@ public class RssFeedManageActivity extends BriarActivity
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
adapter.remove(feed); adapter.remove(feed);
} }
}); });

View File

@@ -190,6 +190,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
} }
private void loadContacts() { private void loadContacts() {
final int revision = adapter.getRevision();
listener.runOnDbThread(new Runnable() { listener.runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -216,7 +217,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Full load took " + duration + " ms"); LOG.info("Full load took " + duration + " ms");
displayContacts(contacts); displayContacts(revision, contacts);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
@@ -225,12 +226,19 @@ public class ContactListFragment extends BaseFragment implements EventListener {
}); });
} }
private void displayContacts(final List<ContactListItem> contacts) { private void displayContacts(final int revision,
final List<ContactListItem> contacts) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (contacts.isEmpty()) list.showData(); if (revision == adapter.getRevision()) {
else adapter.addAll(contacts); adapter.incrementRevision();
if (contacts.isEmpty()) list.showData();
else adapter.addAll(contacts);
} else {
LOG.info("Concurrent update, reloading");
loadContacts();
}
} }
}); });
} }
@@ -288,6 +296,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(c); int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position); ContactListItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {
@@ -302,6 +311,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(c); int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position); ContactListItem item = adapter.getItemAt(position);
if (item != null) adapter.remove(item); if (item != null) adapter.remove(item);
@@ -313,6 +323,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(c); int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position); ContactListItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {

View File

@@ -289,12 +289,10 @@ public class ConversationActivity extends BriarActivity
contactIdenticonKey = contactIdenticonKey =
contact.getAuthor().getId().getBytes(); contact.getAuthor().getId().getBytes();
} }
boolean connected =
connectionRegistry.isConnected(contactId);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading contact took " + duration + " ms"); LOG.info("Loading contact took " + duration + " ms");
displayContactDetails(connected); displayContactDetails();
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
finishOnUiThread(); finishOnUiThread();
} catch (DbException e) { } catch (DbException e) {
@@ -305,7 +303,7 @@ public class ConversationActivity extends BriarActivity
}); });
} }
private void displayContactDetails(final boolean connected) { private void displayContactDetails() {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -313,7 +311,7 @@ public class ConversationActivity extends BriarActivity
new IdenticonDrawable(contactIdenticonKey)); new IdenticonDrawable(contactIdenticonKey));
toolbarTitle.setText(contactName); toolbarTitle.setText(contactName);
if (connected) { if (connectionRegistry.isConnected(contactId)) {
toolbarStatus.setImageDrawable(ContextCompat toolbarStatus.setImageDrawable(ContextCompat
.getDrawable(ConversationActivity.this, .getDrawable(ConversationActivity.this,
R.drawable.contact_online)); R.drawable.contact_online));
@@ -332,6 +330,7 @@ public class ConversationActivity extends BriarActivity
} }
private void loadMessages() { private void loadMessages() {
final int revision = adapter.getRevision();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -357,7 +356,8 @@ public class ConversationActivity extends BriarActivity
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading messages took " + duration + " ms"); LOG.info("Loading messages took " + duration + " ms");
displayMessages(headers, introductions, invitations); displayMessages(revision, headers, introductions,
invitations);
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
finishOnUiThread(); finishOnUiThread();
} catch (DbException e) { } catch (DbException e) {
@@ -368,56 +368,66 @@ public class ConversationActivity extends BriarActivity
}); });
} }
private void displayMessages(final Collection<PrivateMessageHeader> headers, private void displayMessages(final int revision,
final Collection<PrivateMessageHeader> headers,
final Collection<IntroductionMessage> introductions, final Collection<IntroductionMessage> introductions,
final Collection<InvitationMessage> invitations) { final Collection<InvitationMessage> invitations) {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
textInputView.setSendButtonEnabled(true); if (revision == adapter.getRevision()) {
int size = headers.size() + introductions.size() + adapter.incrementRevision();
invitations.size(); textInputView.setSendButtonEnabled(true);
if (size == 0) { List<ConversationItem> items = createItems(headers,
list.showData(); introductions, invitations);
} else { if (items.isEmpty()) list.showData();
List<ConversationItem> items = new ArrayList<>(size); else adapter.addAll(items);
for (PrivateMessageHeader h : headers) {
ConversationMessageItem item = ConversationItem.from(h);
byte[] body = bodyCache.get(h.getId());
if (body == null) loadMessageBody(h.getId());
else item.setBody(body);
items.add(item);
}
for (IntroductionMessage im : introductions) {
if (im instanceof IntroductionRequest) {
IntroductionRequest ir = (IntroductionRequest) im;
items.add(ConversationItem.from(ir));
} else {
IntroductionResponse ir = (IntroductionResponse) im;
items.add(ConversationItem
.from(ConversationActivity.this,
contactName, ir));
}
}
for (InvitationMessage im : invitations) {
if (im instanceof InvitationRequest) {
InvitationRequest ir = (InvitationRequest) im;
items.add(ConversationItem.from(ir));
} else if (im instanceof InvitationResponse) {
InvitationResponse ir = (InvitationResponse) im;
items.add(ConversationItem
.from(ConversationActivity.this,
contactName, ir));
}
}
adapter.addAll(items);
// Scroll to the bottom // Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1); list.scrollToPosition(adapter.getItemCount() - 1);
} else {
LOG.info("Concurrent update, reloading");
loadMessages();
} }
} }
}); });
} }
private List<ConversationItem> createItems(
Collection<PrivateMessageHeader> headers,
Collection<IntroductionMessage> introductions,
Collection<InvitationMessage> invitations) {
int size = headers.size() + introductions.size() + invitations.size();
List<ConversationItem> items = new ArrayList<>(size);
for (PrivateMessageHeader h : headers) {
ConversationMessageItem item = ConversationItem.from(h);
byte[] body = bodyCache.get(h.getId());
if (body == null) loadMessageBody(h.getId());
else item.setBody(body);
items.add(item);
}
for (IntroductionMessage im : introductions) {
if (im instanceof IntroductionRequest) {
IntroductionRequest ir = (IntroductionRequest) im;
items.add(ConversationItem.from(ir));
} else {
IntroductionResponse ir = (IntroductionResponse) im;
items.add(ConversationItem.from(ConversationActivity.this,
contactName, ir));
}
}
for (InvitationMessage im : invitations) {
if (im instanceof InvitationRequest) {
InvitationRequest ir = (InvitationRequest) im;
items.add(ConversationItem.from(ir));
} else if (im instanceof InvitationResponse) {
InvitationResponse ir = (InvitationResponse) im;
items.add(ConversationItem.from(ConversationActivity.this,
contactName, ir));
}
}
return items;
}
private void loadMessageBody(final MessageId m) { private void loadMessageBody(final MessageId m) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
@@ -461,6 +471,7 @@ public class ConversationActivity extends BriarActivity
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
adapter.add(item); adapter.add(item);
// Scroll to the bottom // Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1); list.scrollToPosition(adapter.getItemCount() - 1);
@@ -535,13 +546,13 @@ public class ConversationActivity extends BriarActivity
ContactConnectedEvent c = (ContactConnectedEvent) e; ContactConnectedEvent c = (ContactConnectedEvent) e;
if (c.getContactId().equals(contactId)) { if (c.getContactId().equals(contactId)) {
LOG.info("Contact connected"); LOG.info("Contact connected");
displayContactDetails(true); displayContactDetails();
} }
} else if (e instanceof ContactDisconnectedEvent) { } else if (e instanceof ContactDisconnectedEvent) {
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e; ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
if (c.getContactId().equals(contactId)) { if (c.getContactId().equals(contactId)) {
LOG.info("Contact disconnected"); LOG.info("Contact disconnected");
displayContactDetails(false); displayContactDetails();
} }
} else if (e instanceof IntroductionRequestReceivedEvent) { } else if (e instanceof IntroductionRequestReceivedEvent) {
IntroductionRequestReceivedEvent event = IntroductionRequestReceivedEvent event =
@@ -589,6 +600,7 @@ public class ConversationActivity extends BriarActivity
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
Set<MessageId> messages = new HashSet<>(messageIds); Set<MessageId> messages = new HashSet<>(messageIds);
SparseArray<OutgoingItem> list = adapter.getOutgoingMessages(); SparseArray<OutgoingItem> list = adapter.getOutgoingMessages();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
@@ -807,13 +819,11 @@ public class ConversationActivity extends BriarActivity
timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try { try {
if (accept) { if (accept) {
introductionManager introductionManager.acceptIntroduction(contactId,
.acceptIntroduction(contactId, sessionId, sessionId, timestamp);
timestamp);
} else { } else {
introductionManager introductionManager.declineIntroduction(contactId,
.declineIntroduction(contactId, sessionId, sessionId, timestamp);
timestamp);
} }
loadMessages(); loadMessages();
} catch (DbException | FormatException e) { } catch (DbException | FormatException e) {

View File

@@ -147,7 +147,7 @@ public class ForumActivity extends
private void showUnsubscribeDialog() { private void showUnsubscribeDialog() {
OnClickListener okListener = new OnClickListener() { OnClickListener okListener = new OnClickListener() {
@Override @Override
public void onClick(final DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
deleteNamedGroup(); deleteNamedGroup();
} }
}; };
@@ -162,8 +162,7 @@ public class ForumActivity extends
private void deleteNamedGroup() { private void deleteNamedGroup() {
forumController.deleteNamedGroup( forumController.deleteNamedGroup(
new UiResultExceptionHandler<Void, DbException>( new UiResultExceptionHandler<Void, DbException>(this) {
ForumActivity.this) {
@Override @Override
public void onResultUi(Void v) { public void onResultUi(Void v) {
Toast.makeText(ForumActivity.this, Toast.makeText(ForumActivity.this,

View File

@@ -150,6 +150,7 @@ public class ForumListFragment extends BaseEventFragment implements
} }
private void loadForums() { private void loadForums() {
final int revision = adapter.getRevision();
listener.runOnDbThread(new Runnable() { listener.runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -168,7 +169,7 @@ public class ForumListFragment extends BaseEventFragment implements
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Full load took " + duration + " ms"); LOG.info("Full load took " + duration + " ms");
displayForums(forums); displayForums(revision, forums);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
@@ -177,12 +178,19 @@ public class ForumListFragment extends BaseEventFragment implements
}); });
} }
private void displayForums(final Collection<ForumListItem> forums) { private void displayForums(final int revision,
final Collection<ForumListItem> forums) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (forums.isEmpty()) list.showData(); if (revision == adapter.getRevision()) {
else adapter.addAll(forums); adapter.incrementRevision();
if (forums.isEmpty()) list.showData();
else adapter.addAll(forums);
} else {
LOG.info("Concurrent update, reloading");
loadForums();
}
} }
}); });
} }
@@ -254,6 +262,7 @@ public class ForumListFragment extends BaseEventFragment implements
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(g); int position = adapter.findItemPosition(g);
ForumListItem item = adapter.getItemAt(position); ForumListItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {
@@ -268,6 +277,7 @@ public class ForumListFragment extends BaseEventFragment implements
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(g); int position = adapter.findItemPosition(g);
ForumListItem item = adapter.getItemAt(position); ForumListItem item = adapter.getItemAt(position);
if (item != null) adapter.remove(item); if (item != null) adapter.remove(item);

View File

@@ -23,6 +23,7 @@ import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -30,6 +31,7 @@ public class GroupListFragment extends BaseFragment implements
GroupListListener, OnGroupRemoveClickListener { GroupListListener, OnGroupRemoveClickListener {
public final static String TAG = GroupListFragment.class.getName(); public final static String TAG = GroupListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
public static GroupListFragment newInstance() { public static GroupListFragment newInstance() {
return new GroupListFragment(); return new GroupListFragment();
@@ -120,6 +122,7 @@ public class GroupListFragment extends BaseFragment implements
@UiThread @UiThread
@Override @Override
public void onGroupMessageAdded(GroupMessageHeader header) { public void onGroupMessageAdded(GroupMessageHeader header) {
adapter.incrementRevision();
int position = adapter.findItemPosition(header.getGroupId()); int position = adapter.findItemPosition(header.getGroupId());
GroupItem item = adapter.getItemAt(position); GroupItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {
@@ -137,6 +140,7 @@ public class GroupListFragment extends BaseFragment implements
@UiThread @UiThread
@Override @Override
public void onGroupRemoved(GroupId groupId) { public void onGroupRemoved(GroupId groupId) {
adapter.incrementRevision();
adapter.removeItem(groupId); adapter.removeItem(groupId);
} }
@@ -146,13 +150,20 @@ public class GroupListFragment extends BaseFragment implements
} }
private void loadGroups() { private void loadGroups() {
final int revision = adapter.getRevision();
controller.loadGroups( controller.loadGroups(
new UiResultExceptionHandler<Collection<GroupItem>, DbException>( new UiResultExceptionHandler<Collection<GroupItem>, DbException>(
listener) { listener) {
@Override @Override
public void onResultUi(Collection<GroupItem> groups) { public void onResultUi(Collection<GroupItem> groups) {
if (groups.isEmpty()) list.showData(); if (revision == adapter.getRevision()) {
else adapter.addAll(groups); adapter.incrementRevision();
if (groups.isEmpty()) list.showData();
else adapter.addAll(groups);
} else {
LOG.info("Concurrent update, reloading");
loadGroups();
}
} }
@Override @Override

View File

@@ -29,7 +29,7 @@ abstract class InvitationsActivity extends BriarActivity
protected static final Logger LOG = protected static final Logger LOG =
Logger.getLogger(InvitationsActivity.class.getName()); Logger.getLogger(InvitationsActivity.class.getName());
private InvitationAdapter adapter; protected InvitationAdapter adapter;
private BriarRecyclerView list; private BriarRecyclerView list;
@Inject @Inject
@@ -84,6 +84,7 @@ abstract class InvitationsActivity extends BriarActivity
Toast.makeText(this, res, LENGTH_SHORT).show(); Toast.makeText(this, res, LENGTH_SHORT).show();
// remove item and finish if it was the last // remove item and finish if it was the last
adapter.incrementRevision();
adapter.remove(item); adapter.remove(item);
if (adapter.getItemCount() == 0) { if (adapter.getItemCount() == 0) {
supportFinishAfterTransition(); supportFinishAfterTransition();
@@ -102,7 +103,7 @@ abstract class InvitationsActivity extends BriarActivity
abstract protected int getDeclineRes(); abstract protected int getDeclineRes();
protected void displayInvitations( protected void displayInvitations(final int revision,
final Collection<InvitationItem> invitations, final boolean clear) { final Collection<InvitationItem> invitations, final boolean clear) {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
@@ -110,9 +111,13 @@ abstract class InvitationsActivity extends BriarActivity
if (invitations.isEmpty()) { if (invitations.isEmpty()) {
LOG.info("No more invitations available, finishing"); LOG.info("No more invitations available, finishing");
finish(); finish();
} else { } else if (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (clear) adapter.setItems(invitations); if (clear) adapter.setItems(invitations);
else adapter.addAll(invitations); else adapter.addAll(invitations);
} else {
LOG.info("Concurrent update, reloading");
loadInvitations(clear);
} }
} }
}); });

View File

@@ -70,6 +70,7 @@ public class InvitationsBlogActivity extends InvitationsActivity {
@Override @Override
protected void loadInvitations(final boolean clear) { protected void loadInvitations(final boolean clear) {
final int revision = adapter.getRevision();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -80,7 +81,7 @@ public class InvitationsBlogActivity extends InvitationsActivity {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms"); LOG.info("Load took " + duration + " ms");
displayInvitations(invitations, clear); displayInvitations(revision, invitations, clear);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);

View File

@@ -70,6 +70,7 @@ public class InvitationsForumActivity extends InvitationsActivity {
@Override @Override
protected void loadInvitations(final boolean clear) { protected void loadInvitations(final boolean clear) {
final int revision = adapter.getRevision();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -80,7 +81,7 @@ public class InvitationsForumActivity extends InvitationsActivity {
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms"); LOG.info("Load took " + duration + " ms");
displayInvitations(invitations, clear); displayInvitations(revision, invitations, clear);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);

View File

@@ -24,13 +24,15 @@ public abstract class ThreadItemAdapter<I extends ThreadItem>
private final NestedTreeList<I> items = new NestedTreeList<>(); private final NestedTreeList<I> items = new NestedTreeList<>();
private final Map<I, ValueAnimator> animatingItems = new HashMap<>(); private final Map<I, ValueAnimator> animatingItems = new HashMap<>();
private final ThreadItemListener<I> listener;
private final LinearLayoutManager layoutManager;
// highlight not dependant on time // highlight not dependant on time
private I replyItem; private I replyItem;
// temporary highlight // temporary highlight
private I addedItem; private I addedItem;
private final ThreadItemListener<I> listener; private volatile int revision = 0;
private final LinearLayoutManager layoutManager;
public ThreadItemAdapter(ThreadItemListener<I> listener, public ThreadItemAdapter(ThreadItemListener<I> listener,
LinearLayoutManager layoutManager) { LinearLayoutManager layoutManager) {
@@ -290,6 +292,29 @@ public abstract class ThreadItemAdapter<I extends ThreadItem>
animatingItems.remove(item); animatingItems.remove(item);
} }
/**
* Returns the adapter's revision counter. This method should be called on
* any thread before starting an asynchronous load that could overwrite
* other changes to the adapter, and called again on the UI thread before
* applying the changes from the asynchronous load. If the revision has
* changed between the two calls, the asynchronous load should be restarted
* without applying its changes. Otherwise {@link #incrementRevision()}
* should be called before applying the changes.
*/
public int getRevision() {
return revision;
}
/**
* Increments the adapter's revision counter. This method should be called
* on the UI thread before applying any changes to the adapter that could
* be overwritten by an asynchronous load.
*/
@UiThread
public void incrementRevision() {
revision++;
}
protected interface ThreadItemListener<I> { protected interface ThreadItemListener<I> {
void onItemVisible(I item); void onItemVisible(I item);

View File

@@ -29,6 +29,7 @@ import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger;
import static android.support.design.widget.Snackbar.make; import static android.support.design.widget.Snackbar.make;
import static android.view.View.GONE; import static android.view.View.GONE;
@@ -42,6 +43,9 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
protected static final String KEY_INPUT_VISIBILITY = "inputVisibility"; protected static final String KEY_INPUT_VISIBILITY = "inputVisibility";
protected static final String KEY_REPLY_ID = "replyId"; protected static final String KEY_REPLY_ID = "replyId";
private static final Logger LOG =
Logger.getLogger(ThreadListActivity.class.getName());
protected A adapter; protected A adapter;
protected BriarRecyclerView list; protected BriarRecyclerView list;
protected TextInputView textInput; protected TextInputView textInput;
@@ -106,18 +110,24 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
protected abstract void onNamedGroupLoaded(G groupItem); protected abstract void onNamedGroupLoaded(G groupItem);
private void loadItems() { private void loadItems() {
final int revision = adapter.getRevision();
getController().loadItems( getController().loadItems(
new UiResultExceptionHandler<Collection<I>, DbException>( new UiResultExceptionHandler<Collection<I>, DbException>(this) {
this) {
@Override @Override
public void onResultUi(Collection<I> items) { public void onResultUi(Collection<I> items) {
if (items.isEmpty()) { if (revision == adapter.getRevision()) {
list.showData(); adapter.incrementRevision();
if (items.isEmpty()) {
list.showData();
} else {
adapter.setItems(items);
list.showData();
if (replyId != null)
adapter.setReplyItemById(replyId);
}
} else { } else {
adapter.setItems(items); LOG.info("Concurrent update, reloading");
list.showData(); loadItems();
if (replyId != null)
adapter.setReplyItemById(replyId);
} }
} }
@@ -271,6 +281,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
} }
protected void addItem(final I item, boolean isLocal) { protected void addItem(final I item, boolean isLocal) {
adapter.incrementRevision();
adapter.add(item); adapter.add(item);
if (isLocal && adapter.isVisible(item)) { if (isLocal && adapter.isVisible(item)) {
displaySnackbarShort(getItemPostedString()); displaySnackbarShort(getItemPostedString());

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.util;
import android.content.Context; import android.content.Context;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v7.util.SortedList; import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView.Adapter; import android.support.v7.widget.RecyclerView.Adapter;
import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.RecyclerView.ViewHolder;
@@ -16,6 +17,8 @@ public abstract class BriarAdapter<T, V extends ViewHolder>
protected final Context ctx; protected final Context ctx;
protected final SortedList<T> items; protected final SortedList<T> items;
private volatile int revision = 0;
public BriarAdapter(Context ctx, Class<T> c) { public BriarAdapter(Context ctx, Class<T> c) {
this.ctx = ctx; this.ctx = ctx;
this.items = new SortedList<>(c, new SortedList.Callback<T>() { this.items = new SortedList<>(c, new SortedList.Callback<T>() {
@@ -110,4 +113,26 @@ public abstract class BriarAdapter<T, V extends ViewHolder>
return items.size() == 0; return items.size() == 0;
} }
/**
* Returns the adapter's revision counter. This method should be called on
* any thread before starting an asynchronous load that could overwrite
* other changes to the adapter, and called again on the UI thread before
* applying the changes from the asynchronous load. If the revision has
* changed between the two calls, the asynchronous load should be restarted
* without applying its changes. Otherwise {@link #incrementRevision()}
* should be called before applying the changes.
*/
public int getRevision() {
return revision;
}
/**
* Increments the adapter's revision counter. This method should be called
* on the UI thread before applying any changes to the adapter that could
* be overwritten by an asynchronous load.
*/
@UiThread
public void incrementRevision() {
revision++;
}
} }