Skip to content

Commit

Permalink
Conversation list performance (#359)
Browse files Browse the repository at this point in the history
* bump the bindings

* add the bindings

* try new conversation list work

* update the list methods to return a conversation

* update the last message

* new bindings

* fix up the tests

* get on a working library

* fix up the lint

* fix up the dmtest
  • Loading branch information
nplasterer authored Jan 7, 2025
1 parent 20c8c24 commit b9bb6cb
Show file tree
Hide file tree
Showing 13 changed files with 355 additions and 257 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,38 +98,38 @@ class ConversationsTest {
runBlocking { boClient.conversations.newGroup(listOf(caro.walletAddress)) }
val group2 =
runBlocking { boClient.conversations.newGroup(listOf(caro.walletAddress)) }
runBlocking { dm.send("Howdy") }
runBlocking { group2.send("Howdy") }
val dmMessage = runBlocking { dm.send("Howdy") }
val groupMessage = runBlocking { group2.send("Howdy") }
runBlocking { boClient.conversations.syncAllConversations() }
val conversations = runBlocking { boClient.conversations.list() }
val conversationsOrdered =
runBlocking { boClient.conversations.list(order = Conversations.ConversationOrder.LAST_MESSAGE) }
assertEquals(conversations.size, 3)
assertEquals(conversationsOrdered.size, 3)
assertEquals(conversations.map { it.id }, listOf(dm.id, group1.id, group2.id))
assertEquals(conversationsOrdered.map { it.id }, listOf(group2.id, dm.id, group1.id))
assertEquals(conversations.map { it.id }, listOf(group2.id, dm.id, group1.id))
runBlocking {
assertEquals(group2.lastMessage()!!.id, groupMessage)
assertEquals(dm.lastMessage()!!.id, dmMessage)
}
}

@Test
fun testsCanSyncAllConversationsFiltered() {
runBlocking { boClient.conversations.findOrCreateDm(caro.walletAddress) }
val group =
runBlocking { boClient.conversations.newGroup(listOf(caro.walletAddress)) }
assertEquals(runBlocking { boClient.conversations.syncAllConversations() }.toInt(), 3)
assertEquals(
runBlocking { boClient.conversations.syncAllConversations(consentState = ConsentState.ALLOWED) }.toInt(),
2
assert(runBlocking { boClient.conversations.syncAllConversations() }.toInt() >= 2)
assert(
runBlocking { boClient.conversations.syncAllConversations(consentState = ConsentState.ALLOWED) }.toInt() >= 2
)
assert(
runBlocking { boClient.conversations.syncAllConversations(consentState = ConsentState.DENIED) }.toInt() <= 1
)
runBlocking { group.updateConsentState(ConsentState.DENIED) }
assertEquals(
runBlocking { boClient.conversations.syncAllConversations(consentState = ConsentState.ALLOWED) }.toInt(),
1
assert(
runBlocking { boClient.conversations.syncAllConversations(consentState = ConsentState.ALLOWED) }.toInt() <= 2
)
assertEquals(
runBlocking { boClient.conversations.syncAllConversations(consentState = ConsentState.DENIED) }.toInt(),
1
assert(
runBlocking { boClient.conversations.syncAllConversations(consentState = ConsentState.DENIED) }.toInt() <= 2
)
assertEquals(runBlocking { boClient.conversations.syncAllConversations() }.toInt(), 2)
assert(runBlocking { boClient.conversations.syncAllConversations() }.toInt() >= 2)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,8 @@ class DmTest {
runBlocking { group.send("Howdy") }
runBlocking { boClient.conversations.syncAllConversations() }
val conversations = runBlocking { boClient.conversations.listDms() }
val conversationsOrdered =
runBlocking { boClient.conversations.listDms(order = Conversations.ConversationOrder.LAST_MESSAGE) }
assertEquals(conversations.size, 2)
assertEquals(conversationsOrdered.size, 2)
assertEquals(conversations.map { it.id }, listOf(dm1.id, dm2.id))
assertEquals(conversationsOrdered.map { it.id }, listOf(dm2.id, dm1.id))
assertEquals(conversations.map { it.id }, listOf(dm2.id, dm1.id))
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,12 +513,8 @@ class GroupTest {
runBlocking { group2.send("Howdy") }
runBlocking { boClient.conversations.syncAllConversations() }
val conversations = runBlocking { boClient.conversations.listGroups() }
val conversationsOrdered =
runBlocking { boClient.conversations.listGroups(order = Conversations.ConversationOrder.LAST_MESSAGE) }
assertEquals(conversations.size, 2)
assertEquals(conversationsOrdered.size, 2)
assertEquals(conversations.map { it.id }, listOf(group1.id, group2.id))
assertEquals(conversationsOrdered.map { it.id }, listOf(group2.id, group1.id))
}

@Test
Expand Down
4 changes: 2 additions & 2 deletions library/src/main/java/libxmtp-version.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Version: 7d863bde
Version: ab9bb055
Branch: main
Date: 2024-12-20 22:46:38 +0000
Date: 2025-01-07 00:08:33 +0000
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ sealed class Conversation {
}
}

suspend fun lastMessage(): DecodedMessage? {
return when (this) {
is Group -> group.lastMessage()
is Dm -> dm.lastMessage()
}
}

suspend fun members(): List<Member> {
return when (this) {
is Group -> group.members()
Expand Down
90 changes: 31 additions & 59 deletions library/src/main/java/org/xmtp/android/library/Conversations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import kotlinx.coroutines.launch
import org.xmtp.android.library.libxmtp.Message
import uniffi.xmtpv3.FfiConversation
import uniffi.xmtpv3.FfiConversationCallback
import uniffi.xmtpv3.FfiConversationListItem
import uniffi.xmtpv3.FfiConversationType
import uniffi.xmtpv3.FfiConversations
import uniffi.xmtpv3.FfiCreateGroupOptions
import uniffi.xmtpv3.FfiDirection
import uniffi.xmtpv3.FfiGroupPermissionsOptions
import uniffi.xmtpv3.FfiListConversationsOptions
import uniffi.xmtpv3.FfiListMessagesOptions
import uniffi.xmtpv3.FfiMessage
import uniffi.xmtpv3.FfiMessageCallback
import uniffi.xmtpv3.FfiPermissionPolicySet
Expand All @@ -31,11 +30,6 @@ data class Conversations(
private val ffiConversations: FfiConversations,
) {

enum class ConversationOrder {
CREATED_AT,
LAST_MESSAGE;
}

enum class ConversationType {
ALL,
GROUPS,
Expand Down Expand Up @@ -131,7 +125,11 @@ data class Conversations(

// Sync all new and existing conversations data from the network
suspend fun syncAllConversations(consentState: ConsentState? = null): UInt {
return ffiConversations.syncAllConversations(consentState?.let { ConsentState.toFfiConsentState(it) })
return ffiConversations.syncAllConversations(
consentState?.let {
ConsentState.toFfiConsentState(it)
}
)
}

suspend fun newConversation(peerAddress: String): Conversation {
Expand Down Expand Up @@ -160,101 +158,67 @@ data class Conversations(
after: Date? = null,
before: Date? = null,
limit: Int? = null,
order: ConversationOrder = ConversationOrder.CREATED_AT,
consentState: ConsentState? = null,
): List<Group> {
val ffiGroups = ffiConversations.listGroups(
opts = FfiListConversationsOptions(
after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
limit?.toLong(),
consentState?.let { ConsentState.toFfiConsentState(it) }
consentState?.let { ConsentState.toFfiConsentState(it) },
false
)
)
val sortedConversations = sortConversations(ffiGroups, order)

return sortedConversations.map {
Group(client, it)
return ffiGroups.map {
Group(client, it.conversation(), it.lastMessage())
}
}

suspend fun listDms(
after: Date? = null,
before: Date? = null,
limit: Int? = null,
order: ConversationOrder = ConversationOrder.CREATED_AT,
consentState: ConsentState? = null,
): List<Dm> {
val ffiDms = ffiConversations.listDms(
opts = FfiListConversationsOptions(
after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
limit?.toLong(),
consentState?.let { ConsentState.toFfiConsentState(it) }
consentState?.let { ConsentState.toFfiConsentState(it) },
false
)
)
val sortedConversations = sortConversations(ffiDms, order)

return sortedConversations.map {
Dm(client, it)
return ffiDms.map {
Dm(client, it.conversation(), it.lastMessage())
}
}

suspend fun list(
after: Date? = null,
before: Date? = null,
limit: Int? = null,
order: ConversationOrder = ConversationOrder.CREATED_AT,
consentState: ConsentState? = null,
): List<Conversation> {
val ffiConversations = ffiConversations.list(
val ffiConversation = ffiConversations.list(
FfiListConversationsOptions(
after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS),
limit?.toLong(),
consentState?.let { ConsentState.toFfiConsentState(it) }
consentState?.let { ConsentState.toFfiConsentState(it) },
false
)
)

val sortedConversations = sortConversations(ffiConversations, order)

return sortedConversations.map { it.toConversation() }
}

private suspend fun sortConversations(
conversations: List<FfiConversation>,
order: ConversationOrder,
): List<FfiConversation> {
return when (order) {
ConversationOrder.LAST_MESSAGE -> {
conversations.map { conversation ->
val message =
conversation.findMessages(
FfiListMessagesOptions(
null,
null,
1,
null,
FfiDirection.DESCENDING
)
)
.firstOrNull()
conversation to message?.sentAtNs
}.sortedByDescending {
it.second ?: 0L
}.map {
it.first
}
}

ConversationOrder.CREATED_AT -> conversations
}
return ffiConversation.map { it.toConversation() }
}

private suspend fun FfiConversation.toConversation(): Conversation {
return when (conversationType()) {
FfiConversationType.DM -> Conversation.Dm(Dm(client, this))
else -> Conversation.Group(Group(client, this))
private suspend fun FfiConversationListItem.toConversation(): Conversation {
return when (conversation().conversationType()) {
FfiConversationType.DM -> Conversation.Dm(Dm(client, conversation(), lastMessage()))
else -> Conversation.Group(Group(client, conversation(), lastMessage()))
}
}

Expand All @@ -264,7 +228,15 @@ data class Conversations(
override fun onConversation(conversation: FfiConversation) {
launch(Dispatchers.IO) {
when (conversation.conversationType()) {
FfiConversationType.DM -> trySend(Conversation.Dm(Dm(client, conversation)))
FfiConversationType.DM -> trySend(
Conversation.Dm(
Dm(
client,
conversation
)
)
)

else -> trySend(Conversation.Group(Group(client, conversation)))
}
}
Expand Down
13 changes: 11 additions & 2 deletions library/src/main/java/org/xmtp/android/library/Dm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import uniffi.xmtpv3.FfiMessageCallback
import uniffi.xmtpv3.FfiSubscribeException
import java.util.Date

class Dm(val client: Client, private val libXMTPGroup: FfiConversation) {
class Dm(val client: Client, private val libXMTPGroup: FfiConversation, private val ffiLastMessage: FfiMessage? = null) {
val id: String
get() = libXMTPGroup.id().toHex()

Expand Down Expand Up @@ -103,6 +103,14 @@ class Dm(val client: Client, private val libXMTPGroup: FfiConversation) {
libXMTPGroup.sync()
}

suspend fun lastMessage(): DecodedMessage? {
return if (ffiLastMessage != null) {
Message(client, ffiLastMessage).decode()
} else {
messages(limit = 1).firstOrNull()
}
}

suspend fun messages(
limit: Int? = null,
beforeNs: Long? = null,
Expand All @@ -124,7 +132,8 @@ class Dm(val client: Client, private val libXMTPGroup: FfiConversation) {
direction = when (direction) {
SortDirection.ASCENDING -> FfiDirection.ASCENDING
else -> FfiDirection.DESCENDING
}
},
contentTypes = null
)
).mapNotNull {
Message(client, it).decodeOrNull()
Expand Down
17 changes: 15 additions & 2 deletions library/src/main/java/org/xmtp/android/library/Group.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionOption
import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionPolicySet
import java.util.Date

class Group(val client: Client, private val libXMTPGroup: FfiConversation) {
class Group(
val client: Client,
private val libXMTPGroup: FfiConversation,
private val ffiLastMessage: FfiMessage? = null,
) {
val id: String
get() = libXMTPGroup.id().toHex()

Expand Down Expand Up @@ -120,6 +124,14 @@ class Group(val client: Client, private val libXMTPGroup: FfiConversation) {
libXMTPGroup.sync()
}

suspend fun lastMessage(): DecodedMessage? {
return if (ffiLastMessage != null) {
Message(client, ffiLastMessage).decode()
} else {
messages(limit = 1).firstOrNull()
}
}

suspend fun messages(
limit: Int? = null,
beforeNs: Long? = null,
Expand All @@ -141,7 +153,8 @@ class Group(val client: Client, private val libXMTPGroup: FfiConversation) {
direction = when (direction) {
SortDirection.ASCENDING -> FfiDirection.ASCENDING
else -> FfiDirection.DESCENDING
}
},
contentTypes = null
)
).mapNotNull {
Message(client, it).decodeOrNull()
Expand Down
Loading

0 comments on commit b9bb6cb

Please sign in to comment.