diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
index eb655209108c6a290394c31c3fc3241d4f711c12..d70bdb14e6d9ddfd550af069c35ac1d41777292f 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
@@ -32,6 +32,7 @@ import org.briarproject.api.identity.Author;
 import org.briarproject.api.sync.GroupId;
 
 import java.util.Collection;
+import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
@@ -51,6 +52,7 @@ public class BlogFragment extends BaseFragment implements
 		OnBlogPostAddedListener {
 
 	public final static String TAG = BlogFragment.class.getName();
+	private static final Logger LOG = Logger.getLogger(TAG);
 
 	@Inject
 	BlogController blogController;
@@ -207,6 +209,7 @@ public class BlogFragment extends BaseFragment implements
 						listener) {
 					@Override
 					public void onResultUi(BlogPostItem post) {
+						adapter.incrementRevision();
 						adapter.add(post);
 						if (local) {
 							list.scrollToPosition(0);
@@ -228,16 +231,23 @@ public class BlogFragment extends BaseFragment implements
 	}
 
 	void loadBlogPosts(final boolean reload) {
+		final int revision = adapter.getRevision();
 		blogController.loadBlogPosts(
 				new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
 						listener) {
 					@Override
 					public void onResultUi(Collection<BlogPostItem> posts) {
-						if (posts.isEmpty()) {
-							list.showData();
+						if (revision == adapter.getRevision()) {
+							adapter.incrementRevision();
+							if (posts.isEmpty()) {
+								list.showData();
+							} else {
+								adapter.addAll(posts);
+								if (reload) list.scrollToPosition(0);
+							}
 						} else {
-							adapter.addAll(posts);
-							if (reload) list.scrollToPosition(0);
+							LOG.info("Concurrent update, reloading");
+							loadBlogPosts(reload);
 						}
 					}
 
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
index 6f960d3c128a310de26d699dbe8919c7f910c100..cf9784160dfb6fbf195a27dc82f9bfc1075999f9 100644
--- a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
@@ -27,6 +27,7 @@ import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.db.DbException;
 
 import java.util.Collection;
+import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
@@ -41,6 +42,7 @@ public class FeedFragment extends BaseFragment implements
 		OnBlogPostClickListener, OnBlogPostAddedListener {
 
 	public final static String TAG = FeedFragment.class.getName();
+	private static final Logger LOG = Logger.getLogger(TAG);
 
 	@Inject
 	FeedController feedController;
@@ -129,14 +131,21 @@ public class FeedFragment extends BaseFragment implements
 	}
 
 	private void loadBlogPosts(final boolean clear) {
+		final int revision = adapter.getRevision();
 		feedController.loadBlogPosts(
 				new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
 						listener) {
 					@Override
 					public void onResultUi(Collection<BlogPostItem> posts) {
-						if (clear) adapter.setItems(posts);
-						else adapter.addAll(posts);
-						if (posts.isEmpty()) list.showData();
+						if (revision == adapter.getRevision()) {
+							adapter.incrementRevision();
+							if (clear) adapter.setItems(posts);
+							else adapter.addAll(posts);
+							if (posts.isEmpty()) list.showData();
+						} else {
+							LOG.info("Concurrent update, reloading");
+							loadBlogPosts(clear);
+						}
 					}
 
 					@Override
@@ -193,6 +202,7 @@ public class FeedFragment extends BaseFragment implements
 						listener) {
 					@Override
 					public void onResultUi(BlogPostItem post) {
+						adapter.incrementRevision();
 						adapter.add(post);
 						if (local) {
 							showSnackBar(R.string.blogs_blog_post_created);
diff --git a/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java b/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java
index 0eb6df792e525221d74becf993e0ba3a20665cde..ecffd0cd2007c7349b7a3766685cf93434909ef8 100644
--- a/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java
@@ -125,27 +125,43 @@ public class RssFeedManageActivity extends BriarActivity
 	}
 
 	private void loadFeeds() {
+		final int revision = adapter.getRevision();
 		runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
 				try {
-					addFeeds(feedManager.getFeeds());
+					displayFeeds(revision, feedManager.getFeeds());
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
-					list.setEmptyText(R.string.blogs_rss_feeds_manage_error);
-					list.showData();
+					onLoadError();
 				}
 			}
 		});
 	}
 
-	private void addFeeds(final List<Feed> feeds) {
+	private void displayFeeds(final int revision, final List<Feed> feeds) {
 		runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
 			public void run() {
-				if (feeds.isEmpty()) list.showData();
-				else adapter.addAll(feeds);
+				if (revision == adapter.getRevision()) {
+					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() {
 			@Override
 			public void run() {
+				adapter.incrementRevision();
 				adapter.remove(feed);
 			}
 		});
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
index d640439a68697a7da96bf0ea3aacc48eda1adb0f..474b2cc2b7b7b7f854541eb1128776378b266679 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
@@ -190,6 +190,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 	}
 
 	private void loadContacts() {
+		final int revision = adapter.getRevision();
 		listener.runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
@@ -216,7 +217,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Full load took " + duration + " ms");
-					displayContacts(contacts);
+					displayContacts(revision, contacts);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
 						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() {
 			@Override
 			public void run() {
-				if (contacts.isEmpty()) list.showData();
-				else adapter.addAll(contacts);
+				if (revision == adapter.getRevision()) {
+					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() {
 			@Override
 			public void run() {
+				adapter.incrementRevision();
 				int position = adapter.findItemPosition(c);
 				ContactListItem item = adapter.getItemAt(position);
 				if (item != null) {
@@ -302,6 +311,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 		listener.runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
 			public void run() {
+				adapter.incrementRevision();
 				int position = adapter.findItemPosition(c);
 				ContactListItem item = adapter.getItemAt(position);
 				if (item != null) adapter.remove(item);
@@ -313,6 +323,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 		listener.runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
 			public void run() {
+				adapter.incrementRevision();
 				int position = adapter.findItemPosition(c);
 				ContactListItem item = adapter.getItemAt(position);
 				if (item != null) {
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index fd948d0aadca0ff38cc02730bbe007fe4d5d01d2..c92c5195121ea976f4b64d5a1f0350da3daf1457 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -289,12 +289,10 @@ public class ConversationActivity extends BriarActivity
 						contactIdenticonKey =
 								contact.getAuthor().getId().getBytes();
 					}
-					boolean connected =
-							connectionRegistry.isConnected(contactId);
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Loading contact took " + duration + " ms");
-					displayContactDetails(connected);
+					displayContactDetails();
 				} catch (NoSuchContactException e) {
 					finishOnUiThread();
 				} catch (DbException e) {
@@ -305,7 +303,7 @@ public class ConversationActivity extends BriarActivity
 		});
 	}
 
-	private void displayContactDetails(final boolean connected) {
+	private void displayContactDetails() {
 		runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
 			public void run() {
@@ -313,7 +311,7 @@ public class ConversationActivity extends BriarActivity
 						new IdenticonDrawable(contactIdenticonKey));
 				toolbarTitle.setText(contactName);
 
-				if (connected) {
+				if (connectionRegistry.isConnected(contactId)) {
 					toolbarStatus.setImageDrawable(ContextCompat
 							.getDrawable(ConversationActivity.this,
 									R.drawable.contact_online));
@@ -332,6 +330,7 @@ public class ConversationActivity extends BriarActivity
 	}
 
 	private void loadMessages() {
+		final int revision = adapter.getRevision();
 		runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
@@ -357,7 +356,8 @@ public class ConversationActivity extends BriarActivity
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Loading messages took " + duration + " ms");
-					displayMessages(headers, introductions, invitations);
+					displayMessages(revision, headers, introductions,
+							invitations);
 				} catch (NoSuchContactException e) {
 					finishOnUiThread();
 				} 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<InvitationMessage> invitations) {
 		runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
 			public void run() {
-				textInputView.setSendButtonEnabled(true);
-				int size = headers.size() + introductions.size() +
-						invitations.size();
-				if (size == 0) {
-					list.showData();
-				} else {
-					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));
-						}
-					}
-					adapter.addAll(items);
+				if (revision == adapter.getRevision()) {
+					adapter.incrementRevision();
+					textInputView.setSendButtonEnabled(true);
+					List<ConversationItem> items = createItems(headers,
+							introductions, invitations);
+					if (items.isEmpty()) list.showData();
+					else adapter.addAll(items);
 					// Scroll to the bottom
 					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) {
 		runOnDbThread(new Runnable() {
 			@Override
@@ -461,6 +471,7 @@ public class ConversationActivity extends BriarActivity
 		runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
 			public void run() {
+				adapter.incrementRevision();
 				adapter.add(item);
 				// Scroll to the bottom
 				list.scrollToPosition(adapter.getItemCount() - 1);
@@ -535,13 +546,13 @@ public class ConversationActivity extends BriarActivity
 			ContactConnectedEvent c = (ContactConnectedEvent) e;
 			if (c.getContactId().equals(contactId)) {
 				LOG.info("Contact connected");
-				displayContactDetails(true);
+				displayContactDetails();
 			}
 		} else if (e instanceof ContactDisconnectedEvent) {
 			ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
 			if (c.getContactId().equals(contactId)) {
 				LOG.info("Contact disconnected");
-				displayContactDetails(false);
+				displayContactDetails();
 			}
 		} else if (e instanceof IntroductionRequestReceivedEvent) {
 			IntroductionRequestReceivedEvent event =
@@ -589,6 +600,7 @@ public class ConversationActivity extends BriarActivity
 		runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
 			public void run() {
+				adapter.incrementRevision();
 				Set<MessageId> messages = new HashSet<>(messageIds);
 				SparseArray<OutgoingItem> list = adapter.getOutgoingMessages();
 				for (int i = 0; i < list.size(); i++) {
@@ -807,13 +819,11 @@ public class ConversationActivity extends BriarActivity
 				timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
 				try {
 					if (accept) {
-						introductionManager
-								.acceptIntroduction(contactId, sessionId,
-										timestamp);
+						introductionManager.acceptIntroduction(contactId,
+								sessionId, timestamp);
 					} else {
-						introductionManager
-								.declineIntroduction(contactId, sessionId,
-										timestamp);
+						introductionManager.declineIntroduction(contactId,
+								sessionId, timestamp);
 					}
 					loadMessages();
 				} catch (DbException | FormatException e) {
diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java
index 03ebdf438e960042e49012b49a0c3f3e9ce53eca..12487efc946fa28c551df033daf29f9a76920687 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java
@@ -147,7 +147,7 @@ public class ForumActivity extends
 	private void showUnsubscribeDialog() {
 		OnClickListener okListener = new OnClickListener() {
 			@Override
-			public void onClick(final DialogInterface dialog, int which) {
+			public void onClick(DialogInterface dialog, int which) {
 				deleteNamedGroup();
 			}
 		};
@@ -162,8 +162,7 @@ public class ForumActivity extends
 
 	private void deleteNamedGroup() {
 		forumController.deleteNamedGroup(
-				new UiResultExceptionHandler<Void, DbException>(
-						ForumActivity.this) {
+				new UiResultExceptionHandler<Void, DbException>(this) {
 					@Override
 					public void onResultUi(Void v) {
 						Toast.makeText(ForumActivity.this,
diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
index 0edb1a0755a26c801fc92d2aca220008257835ae..6bbf1965776b3ea3c90c7eccce1b2d661d89282c 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
@@ -150,6 +150,7 @@ public class ForumListFragment extends BaseEventFragment implements
 	}
 
 	private void loadForums() {
+		final int revision = adapter.getRevision();
 		listener.runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
@@ -168,7 +169,7 @@ public class ForumListFragment extends BaseEventFragment implements
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Full load took " + duration + " ms");
-					displayForums(forums);
+					displayForums(revision, forums);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
 						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() {
 			@Override
 			public void run() {
-				if (forums.isEmpty()) list.showData();
-				else adapter.addAll(forums);
+				if (revision == adapter.getRevision()) {
+					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() {
 			@Override
 			public void run() {
+				adapter.incrementRevision();
 				int position = adapter.findItemPosition(g);
 				ForumListItem item = adapter.getItemAt(position);
 				if (item != null) {
@@ -268,6 +277,7 @@ public class ForumListFragment extends BaseEventFragment implements
 		listener.runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
 			public void run() {
+				adapter.incrementRevision();
 				int position = adapter.findItemPosition(g);
 				ForumListItem item = adapter.getItemAt(position);
 				if (item != null) adapter.remove(item);
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java
index a786e766c98c70ba1b1456afab2eca3de94548f2..551c8f30bda68e50c95a1b510c2a08d03e08af64 100644
--- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java
@@ -23,6 +23,7 @@ import org.briarproject.api.privategroup.GroupMessageHeader;
 import org.briarproject.api.sync.GroupId;
 
 import java.util.Collection;
+import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
@@ -30,6 +31,7 @@ public class GroupListFragment extends BaseFragment implements
 		GroupListListener, OnGroupRemoveClickListener {
 
 	public final static String TAG = GroupListFragment.class.getName();
+	private static final Logger LOG = Logger.getLogger(TAG);
 
 	public static GroupListFragment newInstance() {
 		return new GroupListFragment();
@@ -120,6 +122,7 @@ public class GroupListFragment extends BaseFragment implements
 	@UiThread
 	@Override
 	public void onGroupMessageAdded(GroupMessageHeader header) {
+		adapter.incrementRevision();
 		int position = adapter.findItemPosition(header.getGroupId());
 		GroupItem item = adapter.getItemAt(position);
 		if (item != null) {
@@ -137,6 +140,7 @@ public class GroupListFragment extends BaseFragment implements
 	@UiThread
 	@Override
 	public void onGroupRemoved(GroupId groupId) {
+		adapter.incrementRevision();
 		adapter.removeItem(groupId);
 	}
 
@@ -146,13 +150,20 @@ public class GroupListFragment extends BaseFragment implements
 	}
 
 	private void loadGroups() {
+		final int revision = adapter.getRevision();
 		controller.loadGroups(
 				new UiResultExceptionHandler<Collection<GroupItem>, DbException>(
 						listener) {
 					@Override
 					public void onResultUi(Collection<GroupItem> groups) {
-						if (groups.isEmpty()) list.showData();
-						else adapter.addAll(groups);
+						if (revision == adapter.getRevision()) {
+							adapter.incrementRevision();
+							if (groups.isEmpty()) list.showData();
+							else adapter.addAll(groups);
+						} else {
+							LOG.info("Concurrent update, reloading");
+							loadGroups();
+						}
 					}
 
 					@Override
diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java
index fc691ac2b0f725854e3adb70a7caa7202de55b59..772be9bf605e7dd472ffb152a2f395b360147988 100644
--- a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java
+++ b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java
@@ -29,7 +29,7 @@ abstract class InvitationsActivity extends BriarActivity
 	protected static final Logger LOG =
 			Logger.getLogger(InvitationsActivity.class.getName());
 
-	private InvitationAdapter adapter;
+	protected InvitationAdapter adapter;
 	private BriarRecyclerView list;
 
 	@Inject
@@ -84,6 +84,7 @@ abstract class InvitationsActivity extends BriarActivity
 		Toast.makeText(this, res, LENGTH_SHORT).show();
 
 		// remove item and finish if it was the last
+		adapter.incrementRevision();
 		adapter.remove(item);
 		if (adapter.getItemCount() == 0) {
 			supportFinishAfterTransition();
@@ -102,7 +103,7 @@ abstract class InvitationsActivity extends BriarActivity
 
 	abstract protected int getDeclineRes();
 
-	protected void displayInvitations(
+	protected void displayInvitations(final int revision,
 			final Collection<InvitationItem> invitations, final boolean clear) {
 		runOnUiThreadUnlessDestroyed(new Runnable() {
 			@Override
@@ -110,9 +111,13 @@ abstract class InvitationsActivity extends BriarActivity
 				if (invitations.isEmpty()) {
 					LOG.info("No more invitations available, finishing");
 					finish();
-				} else {
+				} else if (revision == adapter.getRevision()) {
+					adapter.incrementRevision();
 					if (clear) adapter.setItems(invitations);
 					else adapter.addAll(invitations);
+				} else {
+					LOG.info("Concurrent update, reloading");
+					loadInvitations(clear);
 				}
 			}
 		});
diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java
index 014391d5b6a8940d0ad35109083209ab2bf679fb..1b0e5f3bd7be5ce10c72991abb40792584d66bd0 100644
--- a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java
+++ b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java
@@ -70,6 +70,7 @@ public class InvitationsBlogActivity extends InvitationsActivity {
 
 	@Override
 	protected void loadInvitations(final boolean clear) {
+		final int revision = adapter.getRevision();
 		runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
@@ -80,7 +81,7 @@ public class InvitationsBlogActivity extends InvitationsActivity {
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Load took " + duration + " ms");
-					displayInvitations(invitations, clear);
+					displayInvitations(revision, invitations, clear);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java
index 6b5811eaac51682ddeb9c4c0ac56214b4fcf7539..d64dab69f04af496aa5469aa79464c8eaa82d5a9 100644
--- a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java
+++ b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java
@@ -70,6 +70,7 @@ public class InvitationsForumActivity extends InvitationsActivity {
 
 	@Override
 	protected void loadInvitations(final boolean clear) {
+		final int revision = adapter.getRevision();
 		runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
@@ -80,7 +81,7 @@ public class InvitationsForumActivity extends InvitationsActivity {
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Load took " + duration + " ms");
-					displayInvitations(invitations, clear);
+					displayInvitations(revision, invitations, clear);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java
index f089f7c7ea148c9f7184eedfea6bd42d0fe7ccf6..034b6924f45500b074dddf1a5421a5a8f598311e 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java
@@ -24,13 +24,15 @@ public abstract class ThreadItemAdapter<I extends ThreadItem>
 
 	private final NestedTreeList<I> items = new NestedTreeList<>();
 	private final Map<I, ValueAnimator> animatingItems = new HashMap<>();
+	private final ThreadItemListener<I> listener;
+	private final LinearLayoutManager layoutManager;
+
 	// highlight not dependant on time
 	private I replyItem;
 	// temporary highlight
 	private I addedItem;
 
-	private final ThreadItemListener<I> listener;
-	private final LinearLayoutManager layoutManager;
+	private volatile int revision = 0;
 
 	public ThreadItemAdapter(ThreadItemListener<I> listener,
 			LinearLayoutManager layoutManager) {
@@ -290,6 +292,29 @@ public abstract class ThreadItemAdapter<I extends ThreadItem>
 		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> {
 
 		void onItemVisible(I item);
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
index 5ae9b5c6ff72b2bf6b806ac03c467274db965e37..6a1ec4883dfcc6ea3c8563aef42f556741b41de0 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
@@ -29,6 +29,7 @@ import org.briarproject.api.sync.MessageId;
 import org.briarproject.util.StringUtils;
 
 import java.util.Collection;
+import java.util.logging.Logger;
 
 import static android.support.design.widget.Snackbar.make;
 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_REPLY_ID = "replyId";
 
+	private static final Logger LOG =
+			Logger.getLogger(ThreadListActivity.class.getName());
+
 	protected A adapter;
 	protected BriarRecyclerView list;
 	protected TextInputView textInput;
@@ -106,18 +110,24 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
 	protected abstract void onNamedGroupLoaded(G groupItem);
 
 	private void loadItems() {
+		final int revision = adapter.getRevision();
 		getController().loadItems(
-				new UiResultExceptionHandler<Collection<I>, DbException>(
-						this) {
+				new UiResultExceptionHandler<Collection<I>, DbException>(this) {
 					@Override
 					public void onResultUi(Collection<I> items) {
-						if (items.isEmpty()) {
-							list.showData();
+						if (revision == adapter.getRevision()) {
+							adapter.incrementRevision();
+							if (items.isEmpty()) {
+								list.showData();
+							} else {
+								adapter.setItems(items);
+								list.showData();
+								if (replyId != null)
+									adapter.setReplyItemById(replyId);
+							}
 						} else {
-							adapter.setItems(items);
-							list.showData();
-							if (replyId != null)
-								adapter.setReplyItemById(replyId);
+							LOG.info("Concurrent update, reloading");
+							loadItems();
 						}
 					}
 
@@ -271,6 +281,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
 	}
 
 	protected void addItem(final I item, boolean isLocal) {
+		adapter.incrementRevision();
 		adapter.add(item);
 		if (isLocal && adapter.isVisible(item)) {
 			displaySnackbarShort(getItemPostedString());
diff --git a/briar-android/src/org/briarproject/android/util/BriarAdapter.java b/briar-android/src/org/briarproject/android/util/BriarAdapter.java
index e9e4b3dbac97de7b4067e1c31905043c46e50c7a..eb566b8bd8e41c27b371ae27d2313d4a6d303f60 100644
--- a/briar-android/src/org/briarproject/android/util/BriarAdapter.java
+++ b/briar-android/src/org/briarproject/android/util/BriarAdapter.java
@@ -2,6 +2,7 @@ package org.briarproject.android.util;
 
 import android.content.Context;
 import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
 import android.support.v7.util.SortedList;
 import android.support.v7.widget.RecyclerView.Adapter;
 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 SortedList<T> items;
 
+	private volatile int revision = 0;
+
 	public BriarAdapter(Context ctx, Class<T> c) {
 		this.ctx = ctx;
 		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;
 	}
 
+	/**
+	 * 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++;
+	}
 }