diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
index f47505f6872e77ccb7717723c4cac6b3a85061b6..2d4080b4ed442b9db065a1b60b79262cb9f2e68c 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
@@ -14,10 +14,12 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent
 import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent
 import org.briarproject.briar.api.conversation.ConversationManager
+import org.briarproject.briar.desktop.utils.clearAndAddAll
 import org.briarproject.briar.desktop.utils.removeFirst
 import org.briarproject.briar.desktop.utils.replaceFirst
 import org.briarproject.briar.desktop.viewmodel.BriarExecutors
 import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel
+import org.briarproject.briar.desktop.viewmodel.UiExecutor
 
 abstract class ContactsViewModel(
     protected val contactManager: ContactManager,
@@ -41,27 +43,32 @@ abstract class ContactsViewModel(
     protected open fun filterContactItem(contactItem: ContactItem) = true
 
     open fun loadContacts() {
-        _fullContactList.apply {
-            clear()
-            addAll(
-                contactManager.contacts.map { contact ->
+        loadOnDbThreadWithTransaction(
+            task = { txn ->
+                contactManager.getContacts(txn).map { contact ->
                     ContactItem(
                         contact,
                         connectionRegistry.isConnected(contact.id),
-                        conversationManager.getGroupCount(contact.id),
+                        conversationManager.getGroupCount(txn, contact.id),
                     )
                 }
-            )
-        }
-        updateFilteredList()
+            },
+            onResult = { contactList ->
+                _fullContactList.clearAndAddAll(contactList)
+                updateFilteredList()
+            },
+            onError = { e ->
+                LOG.error("Error while loading contacts", e)
+            }
+        )
     }
 
     // todo: when migrated to StateFlow, this could be done implicitly instead
+    @UiExecutor
     protected open fun updateFilteredList() {
-        _filteredContactList.apply {
-            clear()
-            addAll(_fullContactList.filter(::filterContactItem).sortedByDescending { it.timestamp })
-        }
+        _filteredContactList.clearAndAddAll(
+            _fullContactList.filter(::filterContactItem).sortedByDescending { it.timestamp }
+        )
     }
 
     override fun eventOccurred(e: Event?) {
@@ -85,11 +92,13 @@ abstract class ContactsViewModel(
         }
     }
 
+    @UiExecutor
     protected open fun updateItem(contactId: ContactId, update: (ContactItem) -> ContactItem) {
         _fullContactList.replaceFirst({ it.contactId == contactId }, update)
         updateFilteredList()
     }
 
+    @UiExecutor
     protected open fun removeItem(contactId: ContactId) {
         _fullContactList.removeFirst { it.contactId == contactId }
         updateFilteredList()
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt
index 5f4b3cd60976c37d7bb95ecfcf8bef46ce72024e..0ca3ae7a713f35af520fb738f6619628c70ac9d5 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt
@@ -1,5 +1,10 @@
 package org.briarproject.briar.desktop.utils
 
+fun <T> MutableList<T>.clearAndAddAll(elements: Collection<T>) {
+    clear()
+    addAll(elements)
+}
+
 fun <T> MutableList<T>.replaceIf(predicate: (T) -> Boolean, transformation: (T) -> T) {
     val li = listIterator()
     while (li.hasNext()) {