diff --git a/briar-android/res/layout/activity_fragment_container.xml b/briar-android/res/layout/activity_fragment_container.xml
index 8bf59ee5acc2ae1186ebae445268a3028fbb7bae..e6c20760fb5078de5def86eec38402fc2db9042a 100644
--- a/briar-android/res/layout/activity_fragment_container.xml
+++ b/briar-android/res/layout/activity_fragment_container.xml
@@ -3,14 +3,4 @@
 	android:id="@+id/fragmentContainer"
 	xmlns:android="http://schemas.android.com/apk/res/android"
 	android:layout_width="match_parent"
-	android:layout_height="match_parent">
-
-	<ProgressBar
-		android:id="@+id/progressBar"
-		style="?android:attr/progressBarStyleLarge"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"
-		android:layout_gravity="center"
-		android:visibility="invisible"/>
-
-</FrameLayout>
\ No newline at end of file
+	android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/briar-android/src/org/briarproject/android/blogs/BaseController.java b/briar-android/src/org/briarproject/android/blogs/BaseController.java
index a7cf1e4243eebaa076e59f7ecedc373b5cf2251c..bfd1a0b13ae9bbeac959058b58c67e1173b0269d 100644
--- a/briar-android/src/org/briarproject/android/blogs/BaseController.java
+++ b/briar-android/src/org/briarproject/android/blogs/BaseController.java
@@ -11,7 +11,7 @@ import org.briarproject.api.sync.MessageId;
 
 import java.util.Collection;
 
-public interface BaseController {
+interface BaseController {
 
 	@UiThread
 	void onStart();
diff --git a/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java b/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
index 06153e422f286c8278c811af08c4d1b53e32a9d0..85c5dff3359c65e651c4e7e00ed41e8de818610f 100644
--- a/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
@@ -20,7 +20,7 @@ import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION;
 
-public abstract class BasePostFragment extends BaseFragment {
+abstract class BasePostFragment extends BaseFragment {
 
 	private final Logger LOG =
 			Logger.getLogger(BasePostFragment.class.getName());
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
index 1b35bc5ced54153325ef84a420fdd238e410f53e..5f4fc731db722f32c588a1114e2ad4b0514998b5 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
@@ -21,8 +21,6 @@ public class BlogActivity extends BriarActivity implements
 	static final String BLOG_NAME = "briar.BLOG_NAME";
 	static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG";
 
-	private ProgressBar progressBar;
-
 	@Inject
 	BlogController blogController;
 
@@ -45,7 +43,6 @@ public class BlogActivity extends BriarActivity implements
 		boolean isNew = i.getBooleanExtra(IS_NEW_BLOG, false);
 
 		setContentView(R.layout.activity_fragment_container);
-		progressBar = (ProgressBar) findViewById(R.id.progressBar);
 
 		if (state == null) {
 			BlogFragment f = BlogFragment.newInstance(groupId, blogName, isNew);
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java b/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java
index b3dea0b8c96a5faf7753d96ceec98efb47c69b25..b463e3130261d81b4a31965befd596ebb7b268e5 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java
@@ -1,7 +1,5 @@
 package org.briarproject.android.blogs;
 
-import android.support.annotation.UiThread;
-
 import org.briarproject.api.blogs.BlogCommentHeader;
 import org.briarproject.api.blogs.BlogPostHeader;
 
@@ -10,7 +8,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
-@UiThread
+// This class is not thread-safe
 class BlogCommentItem extends BlogPostItem {
 
 	private static final BlogCommentComparator COMPARATOR =
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogController.java b/briar-android/src/org/briarproject/android/blogs/BlogController.java
index 4d5c35cbc2d3efc3572cadd6c19983376e671e9b..ba02cacd1c594772b806a4a8cb4c1b37437175dd 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogController.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogController.java
@@ -1,10 +1,6 @@
 package org.briarproject.android.blogs;
 
-import android.support.annotation.UiThread;
-
-import org.briarproject.android.controller.ActivityLifecycleController;
 import org.briarproject.android.controller.handler.ResultExceptionHandler;
-import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
index ad7b7597d2e40387fb7acb3f0e1f518d68810fa8..ebc976468917ecb47d62e1131b3ed61b5b5e162d 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
@@ -3,7 +3,6 @@ package org.briarproject.android.blogs;
 import org.briarproject.android.controller.ActivityLifecycleController;
 import org.briarproject.android.controller.handler.ResultExceptionHandler;
 import org.briarproject.api.blogs.Blog;
-import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.BlogPostAddedEvent;
 import org.briarproject.api.event.Event;
@@ -14,7 +13,6 @@ import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 
 import java.util.Collection;
-import java.util.Collections;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
@@ -108,9 +106,7 @@ public class BlogControllerImpl extends BaseControllerImpl
 					Blog b = blogManager.getBlog(groupId);
 					boolean ours = a.getId().equals(b.getAuthor().getId());
 					boolean removable = blogManager.canBeRemoved(groupId);
-					BlogItem blog = new BlogItem(b,
-							Collections.<BlogPostHeader>emptyList(),
-							ours, removable);
+					BlogItem blog = new BlogItem(b, ours, removable);
 					handler.onResult(blog);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
@@ -119,7 +115,6 @@ public class BlogControllerImpl extends BaseControllerImpl
 				}
 			}
 		});
-
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
index 4d5c1f5ce433f6b6c69c3ce66f6c1a636f2422a6..963b7175ea740ac116deec87081c5d18b5054499 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
@@ -62,7 +62,7 @@ public class BlogFragment extends BaseFragment implements
 	private MenuItem writeButton, deleteButton;
 	private boolean isMyBlog = false, canDeleteBlog = false;
 
-	static BlogFragment newInstance(GroupId groupId, String name,
+	static BlogFragment newInstance(GroupId groupId, @Nullable String name,
 			boolean isNew) {
 
 		BlogFragment f = new BlogFragment();
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogItem.java b/briar-android/src/org/briarproject/android/blogs/BlogItem.java
index 536d05f241f305b9043441521aed197fd464fce0..163840ae23706ed915fdd06a66bdbcc73e72e7ed 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogItem.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogItem.java
@@ -1,40 +1,14 @@
 package org.briarproject.android.blogs;
 
 import org.briarproject.api.blogs.Blog;
-import org.briarproject.api.blogs.BlogPostHeader;
-
-import java.util.Collection;
 
 class BlogItem {
 
 	private final Blog blog;
-	private final int postCount;
-	private final long timestamp;
-	private final int unread;
 	private final boolean ours, removable;
 
-	BlogItem(Blog blog, Collection<BlogPostHeader> headers, boolean ours,
-			boolean removable) {
+	BlogItem(Blog blog, boolean ours, boolean removable) {
 		this.blog = blog;
-		if (headers.isEmpty()) {
-			postCount = 0;
-			timestamp = 0;
-			unread = 0;
-		} else {
-			BlogPostHeader newest = null;
-			long timestamp = -1;
-			int unread = 0;
-			for (BlogPostHeader h : headers) {
-				if (h.getTimestamp() > timestamp) {
-					timestamp = h.getTimestamp();
-					newest = h;
-				}
-				if (!h.isRead()) unread++;
-			}
-			this.postCount = headers.size();
-			this.timestamp = newest.getTimestamp();
-			this.unread = unread;
-		}
 		this.ours = ours;
 		this.removable = removable;
 	}
@@ -47,22 +21,6 @@ class BlogItem {
 		return blog.getName();
 	}
 
-	boolean isEmpty() {
-		return postCount == 0;
-	}
-
-	int getPostCount() {
-		return postCount;
-	}
-
-	long getTimestamp() {
-		return timestamp;
-	}
-
-	int getUnreadCount() {
-		return unread;
-	}
-
 	boolean isOurs() {
 		return ours;
 	}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java b/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
index badf034503d5d99fc57e0a89860d8abe99b9a5f5..ae67d9cfcec82119979d41108c304df58a5afce9 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
@@ -1,60 +1,20 @@
 package org.briarproject.android.blogs;
 
 import android.content.Context;
-import android.support.v7.util.SortedList;
-import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import org.briarproject.R;
+import org.briarproject.android.util.BriarAdapter;
 
-import java.util.Collection;
+class BlogPostAdapter
+		extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
 
-class BlogPostAdapter extends RecyclerView.Adapter<BlogPostViewHolder> {
-
-	private SortedList<BlogPostItem> posts = new SortedList<>(
-			BlogPostItem.class, new SortedList.Callback<BlogPostItem>() {
-		@Override
-		public int compare(BlogPostItem a, BlogPostItem b) {
-			return a.compareTo(b);
-		}
-
-		@Override
-		public void onInserted(int position, int count) {
-			notifyItemRangeInserted(position, count);
-		}
-
-		@Override
-		public void onRemoved(int position, int count) {
-			notifyItemRangeRemoved(position, count);
-		}
-
-		@Override
-		public void onMoved(int fromPosition, int toPosition) {
-			notifyItemMoved(fromPosition, toPosition);
-		}
-
-		@Override
-		public void onChanged(int position, int count) {
-			notifyItemRangeChanged(position, count);
-		}
-
-		@Override
-		public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) {
-			return a.isRead() == b.isRead();
-		}
-
-		@Override
-		public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) {
-			return a.getId().equals(b.getId());
-		}
-	});
-	private final Context ctx;
 	private final OnBlogPostClickListener listener;
 
 	BlogPostAdapter(Context ctx, OnBlogPostClickListener listener) {
-		this.ctx = ctx;
+		super(ctx, BlogPostItem.class);
 		this.listener = listener;
 	}
 
@@ -70,36 +30,22 @@ class BlogPostAdapter extends RecyclerView.Adapter<BlogPostViewHolder> {
 
 	@Override
 	public void onBindViewHolder(BlogPostViewHolder ui, int position) {
-		ui.bindItem(getItem(position));
+		ui.bindItem(getItemAt(position));
 	}
 
 	@Override
-	public int getItemCount() {
-		return posts.size();
-	}
-
-	public BlogPostItem getItem(int position) {
-		return posts.get(position);
+	public int compare(BlogPostItem a, BlogPostItem b) {
+		return a.compareTo(b);
 	}
 
-	public void add(BlogPostItem item) {
-		posts.add(item);
-	}
-
-	public void addAll(Collection<BlogPostItem> items) {
-		posts.addAll(items);
-	}
-
-	public void remove(BlogPostItem item) {
-		posts.remove(item);
-	}
-
-	public void clear() {
-		posts.clear();
+	@Override
+	public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) {
+		return a.isRead() == b.isRead();
 	}
 
-	public boolean isEmpty() {
-		return posts.size() == 0;
+	@Override
+	public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) {
+		return a.getId().equals(b.getId());
 	}
 
 	interface OnBlogPostClickListener {
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
index 747b661593e1d0cb12f6efd2dc73a247c08dcff1..64295c731713576dad6f891dcab7ca0a46b61e04 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
@@ -2,7 +2,6 @@ package org.briarproject.android.blogs;
 
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.UiThread;
 
 import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.identity.Author;
@@ -10,8 +9,8 @@ import org.briarproject.api.identity.Author.Status;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 
-@UiThread
-class BlogPostItem implements Comparable<BlogPostItem> {
+// This class is not thread-safe
+public class BlogPostItem implements Comparable<BlogPostItem> {
 
 	private final BlogPostHeader header;
 	protected String body;
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
index f0a534b84714e8616dc77b07d671167ce76ebb12..560e62e38bacf01221f4addc89bff3eb101ab8d9 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
@@ -12,7 +12,6 @@ import java.util.Collection;
 
 import javax.inject.Inject;
 
-
 public class BlogPostPagerFragment extends BasePostPagerFragment {
 
 	public final static String TAG = BlogPostPagerFragment.class.getName();
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java
index ddf71da9410be1457875b6f55f862e230c8f91df..865b0df35511a763c74b611cbb7b99af3399e0ff 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java
@@ -3,6 +3,7 @@ package org.briarproject.android.blogs;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.app.ActivityOptionsCompat;
@@ -85,7 +86,9 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
 		return "blogPost" + id.hashCode();
 	}
 
-	void bindItem(final BlogPostItem item) {
+	void bindItem(@Nullable final BlogPostItem item) {
+		if (item == null) return;
+
 		setTransitionName(item.getId());
 		if (listener != null) {
 			layout.setClickable(true);
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java
index ab9444104c6b3a84496be03d97cbe9d81a26f941..5f5c351dc1a465dcf880caa26cf22250b48b74f1 100644
--- a/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java
@@ -40,7 +40,7 @@ public class FeedPostPagerFragment extends BasePostPagerFragment {
 		return TAG;
 	}
 
-
+	@Override
 	void loadBlogPosts(final MessageId select) {
 		feedController.loadBlogPosts(
 				new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
@@ -57,6 +57,7 @@ public class FeedPostPagerFragment extends BasePostPagerFragment {
 				});
 	}
 
+	@Override
 	void loadBlogPost(BlogPostHeader header) {
 		feedController.loadBlogPost(header,
 				new UiResultExceptionHandler<BlogPostItem, DbException>(
diff --git a/briar-android/src/org/briarproject/android/blogs/RssFeedAdapter.java b/briar-android/src/org/briarproject/android/blogs/RssFeedAdapter.java
index 2ad13eb343991cda0740c1eb1e4d9d7061851b8c..aa92468158054470d8377118fb1edf3659cf54ed 100644
--- a/briar-android/src/org/briarproject/android/blogs/RssFeedAdapter.java
+++ b/briar-android/src/org/briarproject/android/blogs/RssFeedAdapter.java
@@ -1,8 +1,6 @@
 package org.briarproject.android.blogs;
 
-import android.app.Activity;
-import android.support.annotation.Nullable;
-import android.support.v7.util.SortedList;
+import android.content.Context;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -13,67 +11,18 @@ import android.widget.TextView;
 
 import org.briarproject.R;
 import org.briarproject.android.util.AndroidUtils;
+import org.briarproject.android.util.BriarAdapter;
 import org.briarproject.api.feed.Feed;
-import org.briarproject.api.sync.GroupId;
-
-import java.util.Collection;
 
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
-class RssFeedAdapter extends
-		RecyclerView.Adapter<RssFeedAdapter.FeedViewHolder> {
-
-	private SortedList<Feed> feeds = new SortedList<>(
-			Feed.class, new SortedList.Callback<Feed>() {
-
-		@Override
-		public int compare(Feed a, Feed b) {
-			if (a == b) return 0;
-			long aTime = a.getAdded(), bTime = b.getAdded();
-			if (aTime > bTime) return -1;
-			if (aTime < bTime) return 1;
-			return 0;
-		}
-
-		@Override
-		public void onInserted(int position, int count) {
-			notifyItemRangeInserted(position, count);
-		}
-
-		@Override
-		public void onRemoved(int position, int count) {
-			notifyItemRangeRemoved(position, count);
-		}
-
-		@Override
-		public void onMoved(int fromPosition, int toPosition) {
-			notifyItemMoved(fromPosition, toPosition);
-		}
-
-		@Override
-		public void onChanged(int position, int count) {
-			notifyItemRangeChanged(position, count);
-		}
-
-		@Override
-		public boolean areContentsTheSame(Feed a, Feed b) {
-			return a.getUpdated() == b.getUpdated();
-		}
-
-		@Override
-		public boolean areItemsTheSame(Feed a, Feed b) {
-			return a.getUrl().equals(b.getUrl()) &&
-					a.getBlogId().equals(b.getBlogId()) &&
-					a.getAdded() == b.getAdded();
-		}
-	});
+class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
 
-	private final Activity ctx;
 	private final RssFeedListener listener;
 
-	RssFeedAdapter(Activity ctx, RssFeedListener listener) {
-		this.ctx = ctx;
+	RssFeedAdapter(Context ctx, RssFeedListener listener) {
+		super(ctx, Feed.class);
 		this.listener = listener;
 	}
 
@@ -86,7 +35,8 @@ class RssFeedAdapter extends
 
 	@Override
 	public void onBindViewHolder(FeedViewHolder ui, int position) {
-		final Feed item = getItem(position);
+		final Feed item = getItemAt(position);
+		if (item == null) return;
 
 		// Feed Title
 		if (item.getTitle() != null) {
@@ -128,39 +78,24 @@ class RssFeedAdapter extends
 	}
 
 	@Override
-	public int getItemCount() {
-		return feeds.size();
-	}
-
-	public Feed getItem(int position) {
-		return feeds.get(position);
-	}
-
-	@Nullable
-	public Feed getItem(GroupId g) {
-		for (int i = 0; i < feeds.size(); i++) {
-			Feed item = feeds.get(i);
-			if (item.getBlogId().equals(g)) {
-				return item;
-			}
-		}
-		return null;
-	}
-
-	public void addAll(Collection<Feed> items) {
-		feeds.addAll(items);
+	public int compare(Feed a, Feed b) {
+		if (a == b) return 0;
+		long aTime = a.getAdded(), bTime = b.getAdded();
+		if (aTime > bTime) return -1;
+		if (aTime < bTime) return 1;
+		return 0;
 	}
 
-	public void remove(Feed item) {
-		feeds.remove(item);
-	}
-
-	public void clear() {
-		feeds.clear();
+	@Override
+	public boolean areContentsTheSame(Feed a, Feed b) {
+		return a.getUpdated() == b.getUpdated();
 	}
 
-	public boolean isEmpty() {
-		return feeds.size() == 0;
+	@Override
+	public boolean areItemsTheSame(Feed a, Feed b) {
+		return a.getUrl().equals(b.getUrl()) &&
+				a.getBlogId().equals(b.getBlogId()) &&
+				a.getAdded() == b.getAdded();
 	}
 
 	static class FeedViewHolder extends RecyclerView.ViewHolder {
@@ -172,7 +107,7 @@ class RssFeedAdapter extends
 		private final TextView authorLabel;
 		private final TextView description;
 
-		FeedViewHolder(View v) {
+		private FeedViewHolder(View v) {
 			super(v);
 
 			title = (TextView) v.findViewById(R.id.titleView);
diff --git a/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java b/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java
index a784a9d909ded6e5b72a36965f0493478ab5f362..b1ab777c442989c4735d3b2d210729d4482ba065 100644
--- a/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java
@@ -1,8 +1,8 @@
 package org.briarproject.android.contact;
 
 import android.content.Context;
+import android.support.annotation.Nullable;
 import android.support.v4.view.ViewCompat;
-import android.support.v7.util.SortedList;
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -10,34 +10,32 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.briarproject.R;
+import org.briarproject.android.util.BriarAdapter;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.util.StringUtils;
 
-import java.util.List;
-
 import im.delight.android.identicons.IdenticonDrawable;
 
 import static android.support.v7.util.SortedList.INVALID_POSITION;
 
 public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.BaseContactHolder>
-		extends RecyclerView.Adapter<VH> {
+		extends BriarAdapter<ContactListItem, VH> {
 
-	protected final SortedList<ContactListItem> contacts;
+	@Nullable
 	protected final OnItemClickListener listener;
-	protected Context ctx;
 
-	public BaseContactListAdapter(Context ctx, OnItemClickListener listener) {
-		this.ctx = ctx;
+	public BaseContactListAdapter(Context ctx,
+			@Nullable OnItemClickListener listener) {
+		super(ctx, ContactListItem.class);
 		this.listener = listener;
-		this.contacts = new SortedList<>(ContactListItem.class,
-				new SortedListCallBacks());
 	}
 
 	@Override
 	public void onBindViewHolder(final VH ui, int position) {
-		final ContactListItem item = getItem(position);
+		final ContactListItem item = getItemAt(position);
+		if (item == null) return;
 
 		Author author = item.getContact().getAuthor();
 		ui.avatar.setImageDrawable(
@@ -59,30 +57,37 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
 	}
 
 	@Override
-	public int getItemCount() {
-		return contacts.size();
-	}
-
-	public ContactListItem getItem(int position) {
-		if (position == INVALID_POSITION || contacts.size() <= position) {
-			return null; // Not found
-		}
-		return contacts.get(position);
+	public int compare(ContactListItem c1, ContactListItem c2) {
+		return compareByName(c1, c2);
 	}
 
-	void updateItem(int position, ContactListItem item) {
-		contacts.updateItemAt(position, item);
+	@Override
+	public boolean areItemsTheSame(ContactListItem c1, ContactListItem c2) {
+		return c1.getContact().getId().equals(c2.getContact().getId());
 	}
 
-	public int findItemPosition(ContactListItem c) {
-		return contacts.indexOf(c);
+	@Override
+	public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
+		// check for all properties that influence visual
+		// representation of contact
+		if (c1.isConnected() != c2.isConnected()) {
+			return false;
+		}
+		if (c1.getUnreadCount() != c2.getUnreadCount()) {
+			return false;
+		}
+		if (c1.getTimestamp() != c2.getTimestamp()) {
+			return false;
+		}
+		return true;
 	}
 
 	int findItemPosition(ContactId c) {
 		int count = getItemCount();
 		for (int i = 0; i < count; i++) {
-			ContactListItem item = getItem(i);
-			if (item.getContact().getId().equals(c)) return i;
+			ContactListItem item = getItemAt(i);
+			if (item != null && item.getContact().getId().equals(c))
+				return i;
 		}
 		return INVALID_POSITION; // Not found
 	}
@@ -90,28 +95,13 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
 	int findItemPosition(GroupId g) {
 		int count = getItemCount();
 		for (int i = 0; i < count; i++) {
-			ContactListItem item = getItem(i);
-			if (item.getGroupId().equals(g)) return i;
+			ContactListItem item = getItemAt(i);
+			if (item != null && item.getGroupId().equals(g))
+				return i;
 		}
 		return INVALID_POSITION; // Not found
 	}
 
-	public void addAll(List<ContactListItem> contacts) {
-		this.contacts.addAll(contacts);
-	}
-
-	public void add(ContactListItem contact) {
-		contacts.add(contact);
-	}
-
-	public void remove(ContactListItem contact) {
-		contacts.remove(contact);
-	}
-
-	public void clear() {
-		contacts.clear();
-	}
-
 	public static class BaseContactHolder extends RecyclerView.ViewHolder {
 
 		public final ViewGroup layout;
@@ -127,10 +117,6 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
 		}
 	}
 
-	public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
-		return compareByName(c1, c2);
-	}
-
 	protected int compareByName(ContactListItem c1, ContactListItem c2) {
 		int authorCompare = c1.getLocalAuthor().getName()
 				.compareTo(c2.getLocalAuthor().getName());
@@ -142,7 +128,7 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
 		}
 	}
 
-	int compareByTime(ContactListItem c1, ContactListItem c2) {
+	protected int compareByTime(ContactListItem c1, ContactListItem c2) {
 		long time1 = c1.getTimestamp();
 		long time2 = c2.getTimestamp();
 		if (time1 < time2) return 1;
@@ -150,58 +136,8 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
 		return 0;
 	}
 
-	private class SortedListCallBacks
-			extends SortedList.Callback<ContactListItem> {
-
-		@Override
-		public void onInserted(int position, int count) {
-			notifyItemRangeInserted(position, count);
-		}
-
-		@Override
-		public void onChanged(int position, int count) {
-			notifyItemRangeChanged(position, count);
-		}
-
-		@Override
-		public void onMoved(int fromPosition, int toPosition) {
-			notifyItemMoved(fromPosition, toPosition);
-		}
-
-		@Override
-		public void onRemoved(int position, int count) {
-			notifyItemRangeRemoved(position, count);
-		}
-
-		@Override
-		public int compare(ContactListItem c1, ContactListItem c2) {
-			return compareContactListItems(c1, c2);
-		}
-
-		@Override
-		public boolean areItemsTheSame(ContactListItem c1, ContactListItem c2) {
-			return c1.getContact().getId().equals(c2.getContact().getId());
-		}
-
-		@Override
-		public boolean areContentsTheSame(ContactListItem c1,
-				ContactListItem c2) {
-			// check for all properties that influence visual
-			// representation of contact
-			if (c1.isConnected() != c2.isConnected()) {
-				return false;
-			}
-			if (c1.getUnreadCount() != c2.getUnreadCount()) {
-				return false;
-			}
-			if (c1.getTimestamp() != c2.getTimestamp()) {
-				return false;
-			}
-			return true;
-		}
-	}
-
 	public interface OnItemClickListener {
 		void onItemClick(View view, ContactListItem item);
 	}
+
 }
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
index 6ae5c46d7980c67226f7671441861c80ac597803..a2cf8012d3eae990d857108fa282a63234822805 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java
@@ -31,7 +31,8 @@ public class ContactListAdapter
 	public void onBindViewHolder(ContactHolder ui, int position) {
 		super.onBindViewHolder(ui, position);
 
-		ContactListItem item = getItem(position);
+		ContactListItem item = getItemAt(position);
+		if (item == null) return;
 
 		// unread count
 		int unread = item.getUnreadCount();
@@ -65,11 +66,11 @@ public class ContactListAdapter
 			extends BaseContactListAdapter.BaseContactHolder {
 
 		public final ImageView bulb;
-		final TextView unread;
+		private final TextView unread;
 		public final TextView date;
 		public final TextView identity;
 
-		ContactHolder(View v) {
+		private ContactHolder(View v) {
 			super(v);
 
 			bulb = (ImageView) v.findViewById(R.id.bulbView);
@@ -80,7 +81,7 @@ public class ContactListAdapter
 	}
 
 	@Override
-	public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
+	public int compare(ContactListItem c1, ContactListItem c2) {
 		return compareByTime(c1, c2);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
index 312184f77d9f6d19d22cdc8b8c1d0b5095e11ea4..5feb4d3a15a3014145aec79ac0d0e033e77770b1 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
@@ -97,9 +97,9 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 	protected volatile BlogSharingManager blogSharingManager;
 
 	public static ContactListFragment newInstance() {
-		
+
 		Bundle args = new Bundle();
-		
+
 		ContactListFragment fragment = new ContactListFragment();
 		fragment.setArguments(args);
 		return fragment;
@@ -320,10 +320,10 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 			@Override
 			public void run() {
 				int position = adapter.findItemPosition(c);
-				ContactListItem item = adapter.getItem(position);
+				ContactListItem item = adapter.getItemAt(position);
 				if (item != null) {
 					item.setMessages(messages);
-					adapter.updateItem(position, item);
+					adapter.updateItemAt(position, item);
 				}
 			}
 		});
@@ -334,10 +334,10 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 			@Override
 			public void run() {
 				int position = adapter.findItemPosition(c);
-				ContactListItem item = adapter.getItem(position);
+				ContactListItem item = adapter.getItemAt(position);
 				if (item != null) {
 					item.addMessage(m);
-					adapter.updateItem(position, item);
+					adapter.updateItemAt(position, item);
 				}
 			}
 		});
@@ -348,10 +348,10 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 			@Override
 			public void run() {
 				int position = adapter.findItemPosition(g);
-				ContactListItem item = adapter.getItem(position);
+				ContactListItem item = adapter.getItemAt(position);
 				if (item != null) {
 					item.addMessage(m);
-					adapter.updateItem(position, item);
+					adapter.updateItemAt(position, item);
 				}
 			}
 		});
@@ -362,7 +362,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 			@Override
 			public void run() {
 				int position = adapter.findItemPosition(c);
-				ContactListItem item = adapter.getItem(position);
+				ContactListItem item = adapter.getItemAt(position);
 				if (item != null) adapter.remove(item);
 			}
 		});
@@ -373,7 +373,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 			@Override
 			public void run() {
 				int position = adapter.findItemPosition(c);
-				ContactListItem item = adapter.getItem(position);
+				ContactListItem item = adapter.getItemAt(position);
 				if (item != null) {
 					item.setConnected(connected);
 					adapter.notifyItemChanged(position);
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
index e86189f31a82d3a2fd6a299aa4f3c40d22cda5bf..3fa184bf2f8cd47da961d30e0bd083e65c7e814e 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
@@ -2,7 +2,7 @@ package org.briarproject.android.contact;
 
 import android.content.Context;
 import android.content.Intent;
-import android.support.v7.util.SortedList;
+import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
@@ -16,6 +16,7 @@ import org.briarproject.R;
 import org.briarproject.android.sharing.InvitationsBlogActivity;
 import org.briarproject.android.sharing.InvitationsForumActivity;
 import org.briarproject.android.util.AndroidUtils;
+import org.briarproject.android.util.BriarAdapter;
 import org.briarproject.api.blogs.BlogInvitationRequest;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.forum.ForumInvitationRequest;
@@ -24,9 +25,6 @@ import org.briarproject.api.messaging.PrivateMessageHeader;
 import org.briarproject.api.sharing.InvitationRequest;
 import org.briarproject.util.StringUtils;
 
-import java.util.List;
-
-import static android.support.v7.util.SortedList.INVALID_POSITION;
 import static android.support.v7.widget.RecyclerView.ViewHolder;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
@@ -43,18 +41,13 @@ import static org.briarproject.android.contact.ConversationItem.NOTICE_IN;
 import static org.briarproject.android.contact.ConversationItem.NOTICE_OUT;
 import static org.briarproject.android.contact.ConversationItem.OutgoingItem;
 
-class ConversationAdapter extends RecyclerView.Adapter {
-
-	private final SortedList<ConversationItem> items =
-			new SortedList<>(ConversationItem.class, new ListCallbacks());
+class ConversationAdapter extends BriarAdapter<ConversationItem, ViewHolder> {
 
-	private Context ctx;
 	private IntroductionHandler intro;
 	private String contactName;
 
-	ConversationAdapter(Context context,
-			IntroductionHandler introductionHandler) {
-		ctx = context;
+	ConversationAdapter(Context ctx, IntroductionHandler introductionHandler) {
+		super(ctx, ConversationItem.class);
 		intro = introductionHandler;
 	}
 
@@ -65,7 +58,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
 
 	@Override
 	public int getItemViewType(int position) {
-		return getItem(position).getType();
+		return items.get(position).getType();
 	}
 
 	@Override
@@ -114,7 +107,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
 
 	@Override
 	public void onBindViewHolder(ViewHolder ui, int position) {
-		ConversationItem item = getItem(position);
+		ConversationItem item = getItemAt(position);
 		if (item instanceof ConversationMessageItem) {
 			bindMessage((MessageHolder) ui, (ConversationMessageItem) item);
 		} else if (item instanceof ConversationIntroductionOutItem) {
@@ -370,17 +363,28 @@ class ConversationAdapter extends RecyclerView.Adapter {
 	}
 
 	@Override
-	public int getItemCount() {
-		return items.size();
+	public int compare(ConversationItem c1,
+			ConversationItem c2) {
+		long time1 = c1.getTime();
+		long time2 = c2.getTime();
+		if (time1 < time2) return -1;
+		if (time1 > time2) return 1;
+		return 0;
 	}
 
-	public ConversationItem getItem(int position) {
-		if (position == INVALID_POSITION || items.size() <= position) {
-			return null; // Not found
-		}
-		return items.get(position);
+	@Override
+	public boolean areItemsTheSame(ConversationItem c1,
+			ConversationItem c2) {
+		return c1.getId().equals(c2.getId());
 	}
 
+	@Override
+	public boolean areContentsTheSame(ConversationItem c1,
+			ConversationItem c2) {
+		return c1.equals(c2);
+	}
+
+	@Nullable
 	ConversationItem getLastItem() {
 		if (items.size() > 0) {
 			return items.get(items.size() - 1);
@@ -425,23 +429,11 @@ class ConversationAdapter extends RecyclerView.Adapter {
 		return messages;
 	}
 
-	public void add(final ConversationItem message) {
-		this.items.add(message);
-	}
-
-	public void clear() {
-		items.clear();
-	}
-
-	public void addAll(List<ConversationItem> items) {
-		this.items.addAll(items);
-	}
-
 	private static class MessageHolder extends RecyclerView.ViewHolder {
 
 		public ViewGroup layout;
 		public TextView body;
-		public TextView date;
+		private TextView date;
 		public ImageView status;
 
 		private MessageHolder(View v, int type) {
@@ -532,51 +524,6 @@ class ConversationAdapter extends RecyclerView.Adapter {
 		}
 	}
 
-	private class ListCallbacks extends SortedList.Callback<ConversationItem> {
-
-		@Override
-		public void onInserted(int position, int count) {
-			notifyItemRangeInserted(position, count);
-		}
-
-		@Override
-		public void onChanged(int position, int count) {
-			notifyItemRangeChanged(position, count);
-		}
-
-		@Override
-		public void onMoved(int fromPosition, int toPosition) {
-			notifyItemMoved(fromPosition, toPosition);
-		}
-
-		@Override
-		public void onRemoved(int position, int count) {
-			notifyItemRangeRemoved(position, count);
-		}
-
-		@Override
-		public int compare(ConversationItem c1,
-				ConversationItem c2) {
-			long time1 = c1.getTime();
-			long time2 = c2.getTime();
-			if (time1 < time2) return -1;
-			if (time1 > time2) return 1;
-			return 0;
-		}
-
-		@Override
-		public boolean areItemsTheSame(ConversationItem c1,
-				ConversationItem c2) {
-			return c1.getId().equals(c2.getId());
-		}
-
-		@Override
-		public boolean areContentsTheSame(ConversationItem c1,
-				ConversationItem c2) {
-			return c1.equals(c2);
-		}
-	}
-
 	interface IntroductionHandler {
 		void respondToIntroduction(SessionId sessionId, boolean accept);
 	}
diff --git a/briar-android/src/org/briarproject/android/forum/ForumListAdapter.java b/briar-android/src/org/briarproject/android/forum/ForumListAdapter.java
index d37f23a02933377cf35ee112103910c2abe6d585..9c3ba15fe3d578a3c5269ae79999678bfdf95fda 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumListAdapter.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumListAdapter.java
@@ -4,7 +4,6 @@ import android.content.Context;
 import android.content.Intent;
 import android.support.annotation.Nullable;
 import android.support.v4.content.ContextCompat;
-import android.support.v7.util.SortedList;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -13,73 +12,21 @@ import android.widget.TextView;
 
 import org.briarproject.R;
 import org.briarproject.android.util.AndroidUtils;
+import org.briarproject.android.util.BriarAdapter;
 import org.briarproject.android.view.TextAvatarView;
 import org.briarproject.api.forum.Forum;
 import org.briarproject.api.sync.GroupId;
 
-import java.util.Collection;
-
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 import static org.briarproject.android.BriarActivity.GROUP_ID;
 import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
 
-class ForumListAdapter extends
-		RecyclerView.Adapter<ForumListAdapter.ForumViewHolder> {
-
-	private SortedList<ForumListItem> forums = new SortedList<>(
-			ForumListItem.class, new SortedList.Callback<ForumListItem>() {
-
-		@Override
-		public int compare(ForumListItem a, ForumListItem b) {
-			if (a == b) return 0;
-			// The forum with the newest message comes first
-			long aTime = a.getTimestamp(), bTime = b.getTimestamp();
-			if (aTime > bTime) return -1;
-			if (aTime < bTime) return 1;
-			// Break ties by forum name
-			String aName = a.getForum().getName();
-			String bName = b.getForum().getName();
-			return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
-		}
-
-		@Override
-		public void onInserted(int position, int count) {
-			notifyItemRangeInserted(position, count);
-		}
-
-		@Override
-		public void onRemoved(int position, int count) {
-			notifyItemRangeRemoved(position, count);
-		}
-
-		@Override
-		public void onMoved(int fromPosition, int toPosition) {
-			notifyItemMoved(fromPosition, toPosition);
-		}
-
-		@Override
-		public void onChanged(int position, int count) {
-			notifyItemRangeChanged(position, count);
-		}
-
-		@Override
-		public boolean areContentsTheSame(ForumListItem a, ForumListItem b) {
-			return a.getForum().equals(b.getForum()) &&
-					a.getTimestamp() == b.getTimestamp() &&
-					a.getUnreadCount() == b.getUnreadCount();
-		}
-
-		@Override
-		public boolean areItemsTheSame(ForumListItem a, ForumListItem b) {
-			return a.getForum().equals(b.getForum());
-		}
-	});
-
-	private final Context ctx;
+class ForumListAdapter
+		extends BriarAdapter<ForumListItem, ForumListAdapter.ForumViewHolder> {
 
 	ForumListAdapter(Context ctx) {
-		this.ctx = ctx;
+		super(ctx, ForumListItem.class);
 	}
 
 	@Override
@@ -91,7 +38,8 @@ class ForumListAdapter extends
 
 	@Override
 	public void onBindViewHolder(ForumViewHolder ui, int position) {
-		final ForumListItem item = getItem(position);
+		final ForumListItem item = getItemAt(position);
+		if (item == null) return;
 
 		// Avatar
 		ui.avatar.setText(item.getForum().getName().substring(0, 1));
@@ -142,18 +90,34 @@ class ForumListAdapter extends
 	}
 
 	@Override
-	public int getItemCount() {
-		return forums.size();
+	public int compare(ForumListItem a, ForumListItem b) {
+		if (a == b) return 0;
+		// The forum with the newest message comes first
+		long aTime = a.getTimestamp(), bTime = b.getTimestamp();
+		if (aTime > bTime) return -1;
+		if (aTime < bTime) return 1;
+		// Break ties by forum name
+		String aName = a.getForum().getName();
+		String bName = b.getForum().getName();
+		return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
 	}
 
-	public ForumListItem getItem(int position) {
-		return forums.get(position);
+	@Override
+	public boolean areContentsTheSame(ForumListItem a, ForumListItem b) {
+		return a.getForum().equals(b.getForum()) &&
+				a.getTimestamp() == b.getTimestamp() &&
+				a.getUnreadCount() == b.getUnreadCount();
+	}
+
+	@Override
+	public boolean areItemsTheSame(ForumListItem a, ForumListItem b) {
+		return a.getForum().equals(b.getForum());
 	}
 
 	@Nullable
-	public ForumListItem getItem(GroupId g) {
-		for (int i = 0; i < forums.size(); i++) {
-			ForumListItem item = forums.get(i);
+	public ForumListItem findItem(GroupId g) {
+		for (int i = 0; i < items.size(); i++) {
+			ForumListItem item = items.get(i);
 			if (item.getForum().getGroup().getId().equals(g)) {
 				return item;
 			}
@@ -161,26 +125,10 @@ class ForumListAdapter extends
 		return null;
 	}
 
-	public void addAll(Collection<ForumListItem> items) {
-		forums.addAll(items);
-	}
-
 	void updateItem(ForumListItem item) {
-		ForumListItem oldItem = getItem(item.getForum().getGroup().getId());
-		int position = forums.indexOf(oldItem);
-		forums.updateItemAt(position, item);
-	}
-
-	public void remove(ForumListItem item) {
-		forums.remove(item);
-	}
-
-	public void clear() {
-		forums.clear();
-	}
-
-	public boolean isEmpty() {
-		return forums.size() == 0;
+		ForumListItem oldItem = findItem(item.getForum().getGroup().getId());
+		int position = items.indexOf(oldItem);
+		items.updateItemAt(position, item);
 	}
 
 	static class ForumViewHolder extends RecyclerView.ViewHolder {
@@ -191,7 +139,7 @@ class ForumListAdapter extends
 		private final TextView postCount;
 		private final TextView date;
 
-		ForumViewHolder(View v) {
+		private ForumViewHolder(View v) {
 			super(v);
 
 			layout = (ViewGroup) v;
diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
index fc9f63b37d7436c8a8499e553d0f0779e12cd1ef..94f2e2e4e322b956f9b9a228e7e76059c3a1c401 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
@@ -11,6 +11,7 @@ import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 
 import org.briarproject.R;
@@ -44,7 +45,7 @@ import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
 public class ForumListFragment extends BaseEventFragment implements
-		View.OnClickListener {
+		OnClickListener {
 
 	public final static String TAG = "ForumListFragment";
 
@@ -288,7 +289,7 @@ public class ForumListFragment extends BaseEventFragment implements
 		listener.runOnUiThread(new Runnable() {
 			@Override
 			public void run() {
-				ForumListItem item = adapter.getItem(g);
+				ForumListItem item = adapter.findItem(g);
 				if (item != null) adapter.remove(item);
 			}
 		});
diff --git a/briar-android/src/org/briarproject/android/introduction/ContactChooserAdapter.java b/briar-android/src/org/briarproject/android/introduction/ContactChooserAdapter.java
index a0629d5d2eb9f071104f8a25578adce87cf9cbdb..059ab8cd11e00b8714cd1b6ec94baf97f71b2994 100644
--- a/briar-android/src/org/briarproject/android/introduction/ContactChooserAdapter.java
+++ b/briar-android/src/org/briarproject/android/introduction/ContactChooserAdapter.java
@@ -21,7 +21,8 @@ public class ContactChooserAdapter extends ContactListAdapter {
 	public void onBindViewHolder(final ContactHolder ui, final int position) {
 		super.onBindViewHolder(ui, position);
 
-		final ContactListItem item = getItem(position);
+		final ContactListItem item = getItemAt(position);
+		if (item == null) return;
 
 		ui.name.setText(item.getContact().getAuthor().getName());
 
@@ -34,7 +35,7 @@ public class ContactChooserAdapter extends ContactListAdapter {
 	}
 
 	@Override
-	public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
+	public int compare(ContactListItem c1, ContactListItem c2) {
 		return compareByName(c1, c2);
 	}
 
diff --git a/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java
index 78b0e34b6c3fb852efa5c008603e091cde787a94..7b0e7dfa6e108067aed9470e96f88921216518de 100644
--- a/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java
+++ b/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java
@@ -15,7 +15,9 @@ class BlogInvitationAdapter extends InvitationAdapter {
 	@Override
 	public void onBindViewHolder(InvitationsViewHolder ui, int position) {
 		super.onBindViewHolder(ui, position);
-		InvitationItem item = getItem(position);
+		InvitationItem item = getItemAt(position);
+		if (item == null) return;
+
 		Blog blog = (Blog) item.getShareable();
 
 		ui.avatar.setAuthorAvatar(blog.getAuthor());
@@ -28,7 +30,8 @@ class BlogInvitationAdapter extends InvitationAdapter {
 		}
 	}
 
-	int compareInvitations(InvitationItem o1, InvitationItem o2) {
+	@Override
+	public int compare(InvitationItem o1, InvitationItem o2) {
 		return String.CASE_INSENSITIVE_ORDER
 				.compare(((Blog) o1.getShareable()).getAuthor().getName(),
 						((Blog) o2.getShareable()).getAuthor().getName());
diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorAdapter.java b/briar-android/src/org/briarproject/android/sharing/ContactSelectorAdapter.java
index 422022baa6a8b1476ef22abbe4f0c096ca3eac4f..0d4d68912f5754397e9d417b1a56b63403131021 100644
--- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorAdapter.java
+++ b/briar-android/src/org/briarproject/android/sharing/ContactSelectorAdapter.java
@@ -9,7 +9,6 @@ import android.widget.TextView;
 
 import org.briarproject.R;
 import org.briarproject.android.contact.BaseContactListAdapter;
-import org.briarproject.android.contact.ContactListItem;
 import org.briarproject.api.contact.ContactId;
 
 import java.util.ArrayList;
@@ -41,7 +40,8 @@ class ContactSelectorAdapter
 		super.onBindViewHolder(ui, position);
 
 		SelectableContactListItem item =
-				(SelectableContactListItem) getItem(position);
+				(SelectableContactListItem) getItemAt(position);
+		if (item == null) return;
 
 		if (item.isSelected()) {
 			ui.checkBox.setChecked(true);
@@ -64,9 +64,9 @@ class ContactSelectorAdapter
 	Collection<ContactId> getSelectedContactIds() {
 		Collection<ContactId> selected = new ArrayList<>();
 
-		for (int i = 0; i < contacts.size(); i++) {
+		for (int i = 0; i < items.size(); i++) {
 			SelectableContactListItem item =
-					(SelectableContactListItem) contacts.get(i);
+					(SelectableContactListItem) items.get(i);
 			if (item.isSelected()) selected.add(item.getContact().getId());
 		}
 
@@ -79,7 +79,7 @@ class ContactSelectorAdapter
 		private final CheckBox checkBox;
 		private final TextView shared;
 
-		SelectableContactHolder(View v) {
+		private SelectableContactHolder(View v) {
 			super(v);
 
 			checkBox = (CheckBox) v.findViewById(R.id.checkBox);
@@ -87,11 +87,6 @@ class ContactSelectorAdapter
 		}
 	}
 
-	@Override
-	public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
-		return compareByName(c1, c2);
-	}
-
 	private void grayOutItem(SelectableContactHolder ui, boolean gray) {
 		float alpha = gray ? 0.25f : 1f;
 		ui.avatar.setAlpha(alpha);
diff --git a/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java
index 1535b8cdc0f67c1d7eda269e934243b74721c647..cfe914a9c2949f7b74b42dc508232f2aae2f865a 100644
--- a/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java
+++ b/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java
@@ -14,7 +14,9 @@ class ForumInvitationAdapter extends InvitationAdapter {
 	@Override
 	public void onBindViewHolder(InvitationsViewHolder ui, int position) {
 		super.onBindViewHolder(ui, position);
-		InvitationItem item = getItem(position);
+		InvitationItem item = getItemAt(position);
+		if (item == null) return;
+
 		Forum forum = (Forum) item.getShareable();
 
 		ui.avatar.setText(forum.getName().substring(0, 1));
@@ -23,7 +25,8 @@ class ForumInvitationAdapter extends InvitationAdapter {
 		ui.name.setText(forum.getName());
 	}
 
-	int compareInvitations(InvitationItem o1, InvitationItem o2) {
+	@Override
+	public int compare(InvitationItem o1, InvitationItem o2) {
 		return String.CASE_INSENSITIVE_ORDER
 				.compare(((Forum) o1.getShareable()).getName(),
 						((Forum) o2.getShareable()).getName());
diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java
index 00852f7e9cbb66b90e20cf9fa310b0c7cb27f105..db35fd55bc4dbd0ee3a416d6112167eee42514f1 100644
--- a/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java
+++ b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java
@@ -1,7 +1,6 @@
 package org.briarproject.android.sharing;
 
 import android.content.Context;
-import android.support.v7.util.SortedList;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -10,6 +9,7 @@ import android.widget.Button;
 import android.widget.TextView;
 
 import org.briarproject.R;
+import org.briarproject.android.util.BriarAdapter;
 import org.briarproject.android.view.TextAvatarView;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.sharing.InvitationItem;
@@ -22,16 +22,12 @@ import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
 abstract class InvitationAdapter extends
-		RecyclerView.Adapter<InvitationAdapter.InvitationsViewHolder> {
+		BriarAdapter<InvitationItem, InvitationAdapter.InvitationsViewHolder> {
 
-	protected final Context ctx;
 	private final AvailableForumClickListener listener;
-	private final SortedList<InvitationItem> invitations =
-			new SortedList<>(InvitationItem.class,
-					new SortedListCallBacks());
 
 	InvitationAdapter(Context ctx, AvailableForumClickListener listener) {
-		this.ctx = ctx;
+		super(ctx, InvitationItem.class);
 		this.listener = listener;
 	}
 
@@ -46,7 +42,8 @@ abstract class InvitationAdapter extends
 
 	@Override
 	public void onBindViewHolder(InvitationsViewHolder ui, int position) {
-		final InvitationItem item = getItem(position);
+		final InvitationItem item = getItemAt(position);
+		if (item == null) return;
 
 		Collection<String> names = new ArrayList<>();
 		for (Contact c : item.getNewSharers())
@@ -75,40 +72,28 @@ abstract class InvitationAdapter extends
 	}
 
 	@Override
-	public int getItemCount() {
-		return invitations.size();
+	public boolean areContentsTheSame(InvitationItem oldItem,
+			InvitationItem newItem) {
+		return oldItem.isSubscribed() == newItem.isSubscribed() &&
+				oldItem.getNewSharers().equals(newItem.getNewSharers());
 	}
 
-	public InvitationItem getItem(int position) {
-		return invitations.get(position);
-	}
-
-	public void add(InvitationItem item) {
-		invitations.add(item);
-	}
-
-	public void addAll(Collection<InvitationItem> list) {
-		invitations.addAll(list);
-	}
-
-	public void remove(InvitationItem item) {
-		invitations.remove(item);
-	}
-
-	public void clear() {
-		invitations.clear();
+	@Override
+	public boolean areItemsTheSame(InvitationItem oldItem,
+			InvitationItem newItem) {
+		return oldItem.getShareable().equals(newItem.getShareable());
 	}
 
 	static class InvitationsViewHolder extends RecyclerView.ViewHolder {
 
 		final TextAvatarView avatar;
 		final TextView name;
-		final TextView sharedBy;
+		private final TextView sharedBy;
 		final TextView subscribed;
-		final Button accept;
-		final Button decline;
+		private final Button accept;
+		private final Button decline;
 
-		InvitationsViewHolder(View v) {
+		private InvitationsViewHolder(View v) {
 			super(v);
 
 			avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
@@ -120,52 +105,6 @@ abstract class InvitationAdapter extends
 		}
 	}
 
-	abstract int compareInvitations(InvitationItem o1, InvitationItem o2);
-
-	private class SortedListCallBacks
-			extends SortedList.Callback<InvitationItem> {
-
-		@Override
-		public int compare(InvitationItem o1,
-				InvitationItem o2) {
-			return compareInvitations(o1, o2);
-		}
-
-		@Override
-		public void onInserted(int position, int count) {
-			notifyItemRangeInserted(position, count);
-		}
-
-		@Override
-		public void onRemoved(int position, int count) {
-			notifyItemRangeRemoved(position, count);
-		}
-
-		@Override
-		public void onMoved(int fromPosition, int toPosition) {
-			notifyItemMoved(fromPosition, toPosition);
-		}
-
-		@Override
-		public void onChanged(int position, int count) {
-			notifyItemRangeChanged(position, count);
-		}
-
-		@Override
-		public boolean areContentsTheSame(InvitationItem oldItem,
-				InvitationItem newItem) {
-			return oldItem.isSubscribed() == newItem.isSubscribed() &&
-					oldItem.getNewSharers().equals(newItem.getNewSharers());
-		}
-
-		@Override
-		public boolean areItemsTheSame(InvitationItem oldItem,
-				InvitationItem newItem) {
-			return oldItem.getShareable().equals(newItem.getShareable());
-		}
-	}
-
-
 	interface AvailableForumClickListener {
 		void onItemClick(InvitationItem item, boolean accept);
 	}
diff --git a/briar-android/src/org/briarproject/android/sharing/SharingStatusAdapter.java b/briar-android/src/org/briarproject/android/sharing/SharingStatusAdapter.java
index 18f70fe78b7d4ae28b79b4daf4c1824a7f2fba6a..5f7dd803da443e0751b195d17a469fcff9ae1e41 100644
--- a/briar-android/src/org/briarproject/android/sharing/SharingStatusAdapter.java
+++ b/briar-android/src/org/briarproject/android/sharing/SharingStatusAdapter.java
@@ -7,7 +7,6 @@ import android.view.ViewGroup;
 
 import org.briarproject.R;
 import org.briarproject.android.contact.BaseContactListAdapter;
-import org.briarproject.android.contact.ContactListItem;
 
 class SharingStatusAdapter
 		extends BaseContactListAdapter<BaseContactListAdapter.BaseContactHolder> {
@@ -24,9 +23,4 @@ class SharingStatusAdapter
 		return new BaseContactHolder(v);
 	}
 
-	@Override
-	public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
-		return compareByName(c1, c2);
-	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/util/BriarAdapter.java b/briar-android/src/org/briarproject/android/util/BriarAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..0385c56c362027dad5ced4167a10ef574657d48d
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/util/BriarAdapter.java
@@ -0,0 +1,106 @@
+package org.briarproject.android.util;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.v7.util.SortedList;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+
+import java.util.Collection;
+
+import static android.support.v7.util.SortedList.INVALID_POSITION;
+
+public abstract class BriarAdapter<T, V extends ViewHolder>
+		extends Adapter<V> {
+
+	protected final Context ctx;
+	protected final SortedList<T> items;
+
+	public BriarAdapter(Context ctx, Class<T> c) {
+		this.ctx = ctx;
+		this.items = new SortedList<>(c, new SortedList.Callback<T>() {
+			@Override
+			public int compare(T item1, T item2) {
+				return BriarAdapter.this.compare(item1, item2);
+			}
+
+			@Override
+			public void onInserted(int position, int count) {
+				notifyItemRangeInserted(position, count);
+			}
+
+			@Override
+			public void onRemoved(int position, int count) {
+				notifyItemRangeRemoved(position, count);
+			}
+
+			@Override
+			public void onMoved(int fromPosition, int toPosition) {
+				notifyItemMoved(fromPosition, toPosition);
+			}
+
+			@Override
+			public void onChanged(int position, int count) {
+				notifyItemRangeChanged(position, count);
+			}
+
+			@Override
+			public boolean areContentsTheSame(T item1, T item2) {
+				return BriarAdapter.this.areContentsTheSame(item1, item2);
+			}
+
+			@Override
+			public boolean areItemsTheSame(T item1, T item2) {
+				return BriarAdapter.this.areItemsTheSame(item1, item2);
+			}
+		});
+	}
+
+	public abstract int compare(T item1, T item2);
+
+	public abstract boolean areContentsTheSame(T item1, T item2);
+
+	public abstract boolean areItemsTheSame(T item1, T item2);
+
+	@Override
+	public int getItemCount() {
+		return items.size();
+	}
+
+	public void add(T item) {
+		items.add(item);
+	}
+
+	public void addAll(Collection<T> items) {
+		this.items.addAll(items);
+	}
+
+	@Nullable
+	public T getItemAt(int position) {
+		if (position == INVALID_POSITION || position >= items.size()) {
+			return null;
+		}
+		return items.get(position);
+	}
+
+	public int findItemPosition(T item) {
+		return items.indexOf(item);
+	}
+
+	public void updateItemAt(int position, T item) {
+		items.updateItemAt(position, item);
+	}
+
+	public void remove(T item) {
+		items.remove(item);
+	}
+
+	public void clear() {
+		items.clear();
+	}
+
+	public boolean isEmpty() {
+		return items.size() == 0;
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/view/AuthorView.java b/briar-android/src/org/briarproject/android/view/AuthorView.java
index 40f8e50693e96fc29f25b385c6cfc0242c535dbe..2a1e928cdaad71420f364122fcc3e7d43449b6f2 100644
--- a/briar-android/src/org/briarproject/android/view/AuthorView.java
+++ b/briar-android/src/org/briarproject/android/view/AuthorView.java
@@ -20,7 +20,6 @@ import android.widget.TextView;
 import org.briarproject.R;
 import org.briarproject.android.blogs.BlogActivity;
 import org.briarproject.android.util.AndroidUtils;
-import org.briarproject.android.view.TrustIndicatorView;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.Author.Status;
 import org.briarproject.api.sync.GroupId;