### Run Example Notification Server with FCM Credentials (Bash) Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/push/README.md This snippet demonstrates how to run the example notification server using Go, passing Firebase Cloud Messaging (FCM) credentials and project ID as environment variables or arguments. It assumes you have cloned the notification server repository and obtained the necessary FCM credentials. ```bash YOURFCMJSON=$(cat /path/to/FCMCredentials.json) dev/run \ --xmtp-listener-tls \ --xmtp-listener \ --api \ -x "grpc.production.xmtp.network:443" \ -d "postgres://postgres:xmtp@localhost:25432/postgres?sslmode=disable" \ --fcm-enabled \ --fcm-credentials-json=$YOURFCMJSON \ --fcm-project-id="YOURFCMPROJECTID" ``` -------------------------------- ### Manage XMTP Accounts and Installations Source: https://context7.com/xmtp/xmtp-android/llms.txt Demonstrates how to manage multiple XMTP accounts within a single inbox, remove accounts, and revoke installations. This includes adding new accounts, removing existing ones with a recovery account, revoking specific or all other installations, and checking inbox states. Requires valid wallet signing keys and identity information. ```kotlin import org.xmtp.android.library.XMTPException import org.xmtp.android.library.lib.model.PublicIdentity import org.xmtp.android.library.lib.model.IdentityKind // Assume 'client' is an initialized XMTP client instance // Assume 'newWallet', 'recoveryWallet', 'conversation1', 'conversation2' are defined // Add additional account to existing inbox val newWallet = // ... your new wallet val newSigningKey = WalletSigningKey(newWallet) // Assume WalletSigningKey is defined try { client.addAccount( newAccount = newSigningKey, allowReassignInboxId = false // Prevent reassigning from existing inbox ) println("Account added to inbox") } catch (e: XMTPException) { if (e.message?.contains("already associated") == true) { Log.e("XMTP", "Account already belongs to another inbox") } } // Remove account from inbox (requires recovery account) val recoverySigningKey = WalletSigningKey(recoveryWallet) val identityToRemove = PublicIdentity( IdentityKind.ETHEREUM, "0xAddressToRemove..." ) client.removeAccount( recoverAccount = recoverySigningKey, publicIdentityToRemove = identityToRemove ) // Revoke specific installations val installationsToRevoke = listOf( "installation_id_1", "installation_id_2" ) client.client.revokeInstallations( signingKey = recoverySigningKey, installationIds = installationsToRevoke ) // Revoke all other installations (keep only current) client.revokeAllOtherInstallations(recoverySigningKey) // Check inbox state val inboxState = client.inboxState(refreshFromNetwork = true) println("Inbox ID: ${inboxState.inboxId}") println("Associated addresses:") inboxState.identities.forEach { identity -> println(" ${identity.identifier}") } println("Installation ID: ${inboxState.installationIds.joinToString()}") // Get inbox state for other users val peerInboxIds = listOf("peer_inbox_1", "peer_inbox_2") val peerInboxStates = client.inboxStatesForInboxIds( refreshFromNetwork = true, inboxIds = peerInboxIds ) peerInboxStates.forEach { state -> println("Peer ${state.inboxId} has ${state.identities.size} identities") } // Find inbox ID from identity val walletAddress = "0xAddress..." val publicIdentity = PublicIdentity(IdentityKind.ETHEREUM, walletAddress) val inboxId = client.inboxIdFromIdentity(publicIdentity) println("Inbox ID for $walletAddress: $inboxId") ``` -------------------------------- ### Cryptographic Signing and Verification with Installation Keys (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt Sign and verify messages using installation keys provided by the XMTP client. This is crucial for authentication and authorization workflows. It allows verifying signatures from the same or different installation IDs. ```kotlin import org.xmtp.android.library.Client import org.xmtp.android.library.extensions.toHex import android.util.Log // Sign data with installation key val messageToSign = "Authenticate this action" val signature = client.signWithInstallationKey(messageToSign) println("Signature: ${signature.toHex()}") // Verify signature with installation key val isValid = client.verifySignature( message = messageToSign, signature = signature ) if (isValid) { println("Signature verified!") } else { println("Invalid signature") } // Verify signature from different installation val otherInstallationId = "other_installation_id_hex" val otherSignature = // ... signature from other device val isValidFromOther = client.verifySignatureWithInstallationId( message = messageToSign, signature = otherSignature, installationId = otherInstallationId ) // Use for authentication or authorization fun authenticateAction(action: String, signature: ByteArray): Boolean { return client.verifySignature(action, signature) } // Sign and verify workflow val authChallenge = "grant-access-${System.currentTimeMillis()}" val authSignature = client.signWithInstallationKey(authChallenge) // Later, verify the signed challenge if (client.verifySignature(authChallenge, authSignature)) { // Grant access performPrivilegedAction() } ``` -------------------------------- ### Install xmtp-android SDK using Gradle Source: https://github.com/xmtp/xmtp-android/blob/main/README.md Instructions for adding the xmtp-android SDK dependency to your Android project using Gradle. This is essential for integrating XMTP messaging features into your application. ```gradle implementation 'org.xmtp:android:X.X.X' ``` -------------------------------- ### Use Built-in Codecs for Reactions, Replies, and Attachments in XMTP Android Source: https://context7.com/xmtp/xmtp-android/llms.txt Shows how to use XMTP's built-in codecs for standard message types like reactions, replies, read receipts, and attachments in Android. Includes examples for sending and decoding these message types. ```kotlin import org.xmtp.android.library.codecs.* // Send reaction to a message val reaction = Reaction( reference = originalMessage.id, action = ReactionAction.ADDED, content = "❤️", schema = ReactionSchema.UNICODE ) conversation.send( content = reaction, options = SendOptions(contentType = ContentTypeReaction) ) // Remove reaction val removeReaction = Reaction( reference = originalMessage.id, action = ReactionAction.REMOVED, content = "❤️", schema = ReactionSchema.UNICODE ) conversation.send(removeReaction, SendOptions(contentType = ContentTypeReaction)) // Send reply to message val reply = Reply( reference = originalMessage.id, content = "Great idea!" ) conversation.send( content = reply, options = SendOptions(contentType = ContentTypeReply) ) // Send read receipt val readReceipt = ReadReceipt( timestamp = System.currentTimeMillis() ) conversation.send( content = readReceipt, options = SendOptions(contentType = ContentTypeReadReceipt) ) // Send attachment val attachment = Attachment( filename = "photo.jpg", mimeType = "image/jpeg", data = imageBytes ) conversation.send( content = attachment, options = SendOptions(contentType = ContentTypeAttachment) ) // Decode received messages conversation.messages().forEach { message -> when (message.encodedContent.type.typeId) { "reaction" -> { val reaction = message.content() println("${reaction?.content} ${reaction?.action}") } "reply" -> { val reply = message.content() println("Reply to ${reply?.reference}: ${reply?.content}") } "readReceipt" -> { val receipt = message.content() println("Read at: ${receipt?.timestamp}") } "attachment" -> { val attachment = message.content() println("File: ${attachment?.filename}") } } } ``` -------------------------------- ### Create an Attachment for XMTP Messages Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/codecs/README.md Creates an Attachment object, which represents a file to be sent via XMTP. This involves specifying the filename, MIME type, and the file data as a ByteString. This is a prerequisite for sending attachments. ```kotlin val attachment = Attachment( filename = "test.txt", mimeType = "text/plain", data = "hello world".toByteStringUtf8(), ) ``` -------------------------------- ### Configure and Manage XMTP Debug Logging in Kotlin Source: https://context7.com/xmtp/xmtp-android/llms.txt This snippet demonstrates how to activate persistent debug logging for the XMTP library, set log rotation and file limits, retrieve log file paths, collect logs for support, clear old logs, and disable logging. It also includes examples for retrieving the library version and conversation-specific debug information. ```kotlin import uniffi.xmtpv3.FfiLogLevel import uniffi.xmtpv3.FfiLogRotation import org.xmtp.android.library.ProcessType import java.io.File // Enable persistent logging Client.activatePersistentLibXMTPLogWriter( appContext = applicationContext, logLevel = FfiLogLevel.DEBUG, // or INFO, WARN, ERROR, OFF rotationSchedule = FfiLogRotation.DAILY, // or HOURLY maxFiles = 7, // Keep 7 days of logs processType = ProcessType.MAIN ) // Logs are written to: {filesDir}/xmtp_logs/ // Get log file paths val logFilePaths = Client.getXMTPLogFilePaths(applicationContext) logFilePaths.forEach { path -> println("Log file: $path") } // Send logs for support fun collectLogsForSupport(): List { val logPaths = Client.getXMTPLogFilePaths(applicationContext) return logPaths.map { File(it) } } // Clear old logs val deletedCount = Client.clearXMTPLogs(applicationContext) println("Deleted $deletedCount log files") // Disable logging Client.deactivatePersistentLibXMTPLogWriter() // Get library version info // Assuming 'client' is an initialized XMTP Client instance // println("XMTP Library Version: ${client.libXMTPVersion}") // Get debug information for conversation // Assuming 'conversation' is an initialized XMTP Conversation instance // val debugInfo = conversation.getDebugInformation() // println("Conversation debug info:") // println(" Fork status: ${debugInfo.commitLogForkStatus}") // println(" Last commit: ${debugInfo.lastCommitHash}") // Check if conversation is paused/incompatible // val pausedVersion = conversation.pausedForVersion() // if (pausedVersion != null) { // println("Conversation paused - upgrade to version $pausedVersion required") // } ``` -------------------------------- ### Create Remote Attachment Object Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/codecs/README.md Constructs a RemoteAttachment object from encoded encrypted content and a URL. This involves setting the content length and filename, preparing the attachment for transmission as a remote resource. ```kotlin val remoteAttachment = RemoteAttachment.from( url = URL("https://abcdefg"), encryptedEncodedContent = encodedEncryptedContent ) remoteAttachment.contentLength = attachment.data.size() remoteAttachment.filename = attachment.filename ``` -------------------------------- ### Register XMTP Client for Attachment and Remote Attachment Codecs Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/codecs/README.md Registers the AttachmentCodec and RemoteAttachmentCodec with the XMTP client. This allows the client to encode and decode attachment-related messages. Ensure these codecs are registered before sending or receiving attachments. ```kotlin Client.register(codec = AttachmentCodec()) Client.register(codec = RemoteAttachmentCodec()) ``` -------------------------------- ### Retrieve Messages from XMTP Android Groups Source: https://context7.com/xmtp/xmtp-android/llms.txt Enables querying and filtering messages within XMTP group conversations. This functionality supports fetching recent messages with pagination and sorting, time-based filtering using nanoseconds, retrieving messages that include reactions, and obtaining enriched message data with additional metadata. It also provides methods for counting messages and getting the very last message in a conversation. ```kotlin import org.xmtp.android.library.libxmtp.DecodedMessage import android.util.Log // Get recent messages with pagination try { val messages = group.messages( limit = 50, direction = DecodedMessage.SortDirection.DESCENDING, deliveryStatus = DecodedMessage.MessageDeliveryStatus.PUBLISHED ) messages.forEach { println("From: ${it.senderInboxId}") println("Sent: ${it.sentAt}") println("Body: ${it.body}") println("Delivery: ${it.deliveryStatus}") println("---") } } catch (e: Exception) { Log.e("XMTP", "Failed to fetch messages: ${e.message}", e) } // Time-based filtering val oneDayAgo = System.currentTimeMillis() * 1_000_000 - 86400_000_000_000L val recentMessages = group.messages( afterNs = oneDayAgo, sortBy = DecodedMessage.SortBy.SENT_TIME ) // Get messages with reactions val messagesWithReactions = group.messagesWithReactions(limit = 20) messagesWithReactions.forEach { it.childMessages?.forEach { reaction -> println("Reaction: ${reaction.body}") } } // Get enriched messages (v2 format with additional metadata) val enrichedMessages = group.enrichedMessages(limit = 25) // Count messages val messageCount = group.countMessages( afterNs = oneDayAgo, deliveryStatus = DecodedMessage.MessageDeliveryStatus.ALL ) println("Total messages in last 24h: $messageCount") // Get last message val lastMessage = group.lastMessage() lastMessage?.let { println("Last activity: ${it.sentAt}") } ``` -------------------------------- ### Create Authenticated XMTP Client with Wallet Signing Key (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt Initializes an authenticated XMTP client using a provided wallet's signing key. This process involves creating a custom implementation of the SigningKey interface to integrate with the user's wallet. Client options configure the API environment, database encryption, and other settings. The generated client is then used for sending and receiving messages. ```kotlin import org.xmtp.android.library.Client import org.xmtp.android.library.ClientOptions import org.xmtp.android.library.SigningKey import org.xmtp.android.library.XMTPEnvironment import org.xmtp.android.library.libxmtp.PublicIdentity import org.xmtp.android.library.libxmtp.IdentityKind // Implement SigningKey interface for wallet integration class WalletSigningKey(private val wallet: Wallet) : SigningKey { override val publicIdentity: PublicIdentity get() = PublicIdentity(IdentityKind.ETHEREUM, wallet.address) override suspend fun sign(message: String): SignedData { val signature = wallet.signMessage(message) return SignedData(signature.toByteArray()) } } // Create client options with encryption key val options = ClientOptions( api = ClientOptions.Api( env = XMTPEnvironment.PRODUCTION, // or XMTPEnvironment.DEV for testing isSecure = true, appVersion = "MyApp/1.0.0" ), appContext = applicationContext, dbEncryptionKey = generateSecureRandomBytes(32), // Store securely historySyncUrl = "https://message-history.production.ephemera.network/", deviceSyncEnabled = true ) // Create authenticated client val client = try { Client.create( account = WalletSigningKey(userWallet), options = options ) // Client successfully created and authenticated println("Client created with inboxId: ${client.inboxId}") println("Installation ID: ${client.installationId}") client } catch (e: Exception) { Log.e("XMTP", "Failed to create client: ${e.message}", e) throw e } ``` -------------------------------- ### Send Remote Attachment via XMTP Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/codecs/README.md Sends a RemoteAttachment object within an XMTP conversation. It requires specifying the `ContentTypeRemoteAttachment` in the SendOptions to ensure the recipient client can correctly interpret the message. ```kotlin val newConversation = client.conversations.newConversation(walletAddress) newConversation.send( content = remoteAttachment, options = SendOptions(contentType = ContentTypeRemoteAttachment), ) ``` -------------------------------- ### Format files using Spotless Gradle task Source: https://github.com/xmtp/xmtp-android/blob/main/README.md Applies code formatting rules to the project files using the Spotless plugin via a Gradle task. This ensures consistent code style across the project. ```bash ./gradlew spotlessApply ``` -------------------------------- ### Archive Management: Create, Metadata, and Import (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt Backup and restore conversation data using encrypted archives. This involves creating an archive with specified options, retrieving its metadata, and importing an existing archive to restore data. ```kotlin import org.xmtp.android.library.Client import org.xmtp.android.library.libxmtp.ArchiveOptions import org.xmtp.android.library.libxmtp.ArchiveMetadata import android.content.Context import android.util.Log // Assuming 'client' is an initialized XMTP client instance // Assuming 'applicationContext' is available // Assuming 'uploadToCloud' and 'downloadFromCloud' are defined functions // Assuming 'generateSecureRandomBytes' is available // Create archive (backup) val archivePath = "${applicationContext.filesDir}/xmtp_backup.db3" val archiveEncryptionKey = generateSecureRandomBytes(32) // Store securely val archiveOptions = ArchiveOptions( includeGroups = true, includeDms = true, includeMessages = true, startTimeNs = null, // Include all history endTimeNs = null ) try { client.createArchive( path = archivePath, encryptionKey = archiveEncryptionKey, opts = archiveOptions ) println("Archive created at: $archivePath") // Upload archive to cloud storage // uploadToCloud(archivePath) } catch (e: Exception) { Log.e("XMTP", "Archive creation failed: ${e.message}", e) } // Get archive metadata without full import val metadata = client.archiveMetadata( path = archivePath, encryptionKey = archiveEncryptionKey ) println("Archive created: ${metadata.createdAt}") println("Contains ${metadata.conversationCount} conversations") println("Contains ${metadata.messageCount} messages") // Import archive (restore) try { // Download archive from cloud if needed // downloadFromCloud(archivePath) client.importArchive( path = archivePath, encryptionKey = archiveEncryptionKey ) println("Archive imported successfully") // Sync to get latest updates client.conversations.syncAllConversations() } catch (e: Exception) { Log.e("XMTP", "Archive import failed: ${e.message}", e) } // Create time-range archive val oneMonthAgo = System.currentTimeMillis() * 1_000_000 - (30L * 24 * 60 * 60 * 1_000_000_000L) val recentArchiveOptions = ArchiveOptions( includeGroups = true, includeDms = true, includeMessages = true, startTimeNs = oneMonthAgo, endTimeNs = System.currentTimeMillis() * 1_000_000 ) // client.createArchive( // "${applicationContext.filesDir}/recent_backup.db3", // archiveEncryptionKey, // recentArchiveOptions // ) ``` -------------------------------- ### Build XMTP Client Without Immediate Authentication (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt Builds an XMTP client instance without immediate authentication, which is useful for read-only operations or scenarios where authentication is deferred. It first obtains or generates an inbox ID for a given public identity and then constructs the client. Later, the client can be authenticated by signing a challenge using the wallet. ```kotlin import org.xmtp.android.library.Client import org.xmtp.android.library.ClientOptions import org.xmtp.android.library.libxmtp.PublicIdentity import org.xmtp.android.library.libxmtp.IdentityKind // Create public identity for address lookup val publicIdentity = PublicIdentity( kind = IdentityKind.ETHEREUM, identifier = "0x1234567890abcdef1234567890abcdef12345678" ) // Get or generate inbox ID for this identity val inboxId = Client.getOrCreateInboxId( api = options.api, publicIdentity = publicIdentity ) // Build client without authentication val client = try { Client.build( publicIdentity = publicIdentity, options = options, inboxId = inboxId ) println("Built offline client for inbox: ${client.inboxId}") client } catch (e: Exception) { Log.e("XMTP", "Failed to build client: ${e.message}", e) throw e } // Later, authenticate if needed using ffi methods val signatureRequest = client.ffiSignatureRequest() if (signatureRequest != null) { val signingKey = WalletSigningKey(userWallet) val signedData = signingKey.sign(signatureRequest.signatureText()) signatureRequest.addEcdsaSignature(signedData.rawData) client.ffiRegisterIdentity(signatureRequest) } ``` -------------------------------- ### Encode and Encrypt Attachment for Remote Transport Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/codecs/README.md Encodes and encrypts an Attachment object into a format suitable for remote transport using the AttachmentCodec. This step prepares the attachment data for sending as a remote attachment, ensuring it's secure and properly formatted. ```kotlin val encodedEncryptedContent = RemoteAttachment.encodeEncrypted( content = attachment, codec = AttachmentCodec(), ) ``` -------------------------------- ### Run Debug Lint Checks with Gradle Source: https://github.com/xmtp/xmtp-android/blob/main/README.md Executes the debug lint task for the library module using Gradle. This helps identify potential bugs and code quality issues in the Android application. ```bash ./gradlew :library:lintDebug ``` -------------------------------- ### Receive, Decode, and Decrypt Remote Attachment Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/codecs/README.md Receives a remote attachment message from an XMTP conversation, loads it using a Fetcher, and then decrypts and decodes it into an Attachment object. This process involves fetching the attachment data and making it available for use in the application. ```kotlin val message = newConversation.messages().first() val loadedRemoteAttachment: RemoteAttachment = messages.content() loadedRemoteAttachment.fetcher = Fetcher() runBlocking { val attachment: Attachment = loadedRemoteAttachment.load() } ``` -------------------------------- ### Configure XMTP Push Notifications with Firebase Source: https://context7.com/xmtp/xmtp-android/llms.txt Sets up Firebase Cloud Messaging for XMTP push notifications. It involves initializing XMTPPush, registering the FCM token, subscribing to conversation topics, and handling incoming FCM messages. Dependencies include the XMTP SDK and Firebase Messaging libraries. ```kotlin import org.xmtp.android.library.push.XMTPPush import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.RemoteMessage import android.util.Base64 import android.util.Log import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.launch // Assume 'client' is an initialized XMTP client instance // Initialize XMTPPush val xmtpPush = XMTPPush( context = applicationContext, pushServer = "production.xmtp.network:443" // or your push server ) // Get Firebase token FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> if (task.isSuccessful) { val fcmToken = task.result try { // Register device for push notifications xmtpPush.register(fcmToken) println("Registered for push notifications") // Subscribe to conversation topics val topics = client.conversations.allPushTopics() xmtpPush.subscribe(topics) } catch (e: Exception) { Log.e("XMTP", "Push registration failed: ${e.message}", e) } } } // Subscribe to specific conversations val conversationTopics = listOf( conversation1.getPushTopics(), conversation2.getPushTopics() ).flatten() xmtpPush.subscribe(conversationTopics) // Subscribe with HMAC metadata for enhanced security val hmacKeys = client.conversations.getHmacKeys() val subscriptions = hmacKeys.hmacKeysMap.map { (topic, keys) -> Service.Subscription.newBuilder() // Assuming Service is defined elsewhere in XMTP SDK .setTopic(topic) .addAllHmacKeys(keys.valuesList.map { key -> Service.HmacKey.newBuilder() // Assuming Service is defined elsewhere in XMTP SDK .setKey(key.hmacKey) .setThirtyDayPeriodsSinceEpoch(key.thirtyDayPeriodsSinceEpoch) .build() }) .build() } xmtpPush.subscribeWithMetadata(subscriptions) // Handle incoming FCM messages class MyFirebaseMessagingService : FirebaseMessagingService() { override fun onMessageReceived(remoteMessage: RemoteMessage) { val conversationId = remoteMessage.data["conversationId"] val encryptedMessage = remoteMessage.data["encryptedMessage"] // Decrypt and process message lifecycleScope.launch { val conversation = client.conversations.findConversation(conversationId) encryptedMessage?.let { encrypted -> val message = conversation?.processMessage( Base64.decode(encrypted, Base64.DEFAULT) ) message?.let { showNotification(it) // Assume showNotification is defined } } } } } // Unsubscribe from topics val topicsToUnsubscribe = listOf("topic1", "topic2") xmtpPush.unsubscribe(topicsToUnsubscribe) ``` -------------------------------- ### Create or Find Direct Message (DM) Conversation (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt This snippet illustrates how to create a new one-on-one direct message conversation or retrieve an existing one using the XMTP Android SDK. It covers finding/creating DMs by inbox ID or public identity, and how to handle potential errors like trying to message oneself. It also shows how to wrap a conversation into a `Conversation.Dm` type. ```kotlin import org.xmtp.android.library.Dm import org.xmtp.android.library.PublicIdentity import org.xmtp.android.library.IdentityKind import org.xmtp.android.library.exceptions.XMTPException import org.xmtp.android.library.models.DisappearingMessageSettings import org.xmtp.android.library.core.Conversation import android.util.Log // Assuming 'client' is an initialized XMTP client instance // Find or create DM by inbox ID val peerInboxId = "peer_inbox_id_123" val dm = try { client.conversations.findOrCreateDm( peerInboxId = peerInboxId, disappearingMessageSettings = DisappearingMessageSettings( disappearStartingAtNs = System.currentTimeMillis() * 1_000_000, retentionDurationInNs = 604800_000_000_000L // 7 days ) ) println("DM conversation ID: ${dm.id}") println("Peer inbox: ${dm.peerInboxId}") println("Created at: ${dm.createdAt}") dm } catch (e: XMTPException) { if (e.message?.contains("Recipient is sender") == true) { Log.e("XMTP", "Cannot create DM with yourself") } throw e } // Find or create DM by public identity (wallet address) val peerIdentity = PublicIdentity( IdentityKind.ETHEREUM, "0xPeerAddress..." ) val dmByIdentity = client.conversations.findOrCreateDmWithIdentity( peerPublicIdentity = peerIdentity ) // Find existing DM by inbox ID (returns null if not exists) val existingDm = client.conversations.findDmByInboxId("peer_inbox_id") // Find existing DM by identity val existingDmByIdentity = client.conversations.findDmByIdentity(peerIdentity) // Create conversation wrapper (returns Conversation.Dm) val conversation = client.conversations.newConversation( peerInboxId = peerInboxId ) when (conversation) { is Conversation.Dm -> { val dmInstance = conversation.dm println("DM with: ${dmInstance.peerInboxId}") } else -> {} } ``` -------------------------------- ### Subscribing to Conversation Push Notifications (Kotlin) Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/push/README.md This Kotlin code illustrates how to subscribe to push notifications for specific conversations. It involves fetching HMAC keys, constructing Service.Subscription objects for each conversation, and adding a subscription for welcome messages. Finally, it uses XMTPPush to subscribe with the generated metadata. ```kotlin val hmacKeysResult = ClientManager.client.conversations.getHmacKeys() val subscriptions: MutableList = conversations.map { val hmacKeys = hmacKeysResult.hmacKeysMap val result = hmacKeys[it.topic]?.valuesList?.map { hmacKey -> Service.Subscription.HmacKey.newBuilder().also { sub_key -> sub_key.key = hmacKey.hmacKey sub_key.thirtyDayPeriodsSinceEpoch = hmacKey.thirtyDayPeriodsSinceEpoch }.build() } Service.Subscription.newBuilder().also { sub -> sub.addAllHmacKeys(result) sub.topic = it.topic sub.isSilent = it.version == Conversation.Version.V1 }.build() }.toMutableList() // To get pushes for New Group (WelcomeMessages) val welcomeTopic = Service.Subscription.newBuilder().also { sub -> sub.topic = Topic.userWelcome(ClientManager.client.installationId).description sub.isSilent = false }.build() subscriptions.add(welcomeTopic) XMTPPush(context, "10.0.2.2:8080").subscribeWithMetadata(subscriptions) ``` -------------------------------- ### Send Messages in XMTP Android Groups Source: https://context7.com/xmtp/xmtp-android/llms.txt Demonstrates sending text and custom content types within XMTP groups. It covers basic text messages, sending reactions with specific content types and compression, preparing messages for offline support, and sending messages with custom visibility options to control push notifications. ```kotlin import org.xmtp.android.library.SendOptions import org.xmtp.android.library.codecs.ContentTypeId import org.xmtp.android.library.libxmtp.EncodedContent import org.xmtp.android.library.libxmtp.MessageVisibilityOptions import org.xmtp.android.library.Message import org.xmtp.android.library.Message.ReactionAction import org.xmtp.android.library.Message.ReactionSchema import org.xmtp.android.library.Message.Reaction import org.xmtp.android.library.Message.EncodedContentCompression import android.util.Log // Send simple text message try { val messageId = group.send("Hello team! 👋") println("Message sent with ID: $messageId") } catch (e: Exception) { Log.e("XMTP", "Failed to send message: ${e.message}", e) } // Send with custom content type val options = SendOptions( contentType = ContentTypeId( authorityId = "xmtp.org", typeId = "reaction", versionMajor = 1, versionMinor = 0 ), compression = EncodedContentCompression.GZIP ) val reactionContent = Reaction( reference = "message_id_123", action = ReactionAction.ADDED, content = "👍", schema = ReactionSchema.UNICODE ) val reactionMessageId = group.send( content = reactionContent, options = options ) // Prepare message optimistically (offline support) val preparedId = group.prepareMessage("Queued message") // Later, when online: group.publishMessages() // Publishes all prepared messages // Send with custom visibility options val encodedContent = EncodedContent.newBuilder() .setType(ContentTypeText) .setContent("Sensitive message".toByteStringUtf8()) .build() val visibilityOpts = MessageVisibilityOptions(shouldPush = false) // No push notification group.send(encodedContent, visibilityOpts) ``` -------------------------------- ### Send and Receive Direct Message (DM) Messages (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt This snippet covers the essential operations for sending and receiving messages within a direct message conversation using the XMTP Android SDK. It includes sending text and custom content (like attachments), retrieving message history with sorting and limits, streaming new messages in real-time, counting unread messages, and fetching DM participants. ```kotlin import org.xmtp.android.library.DecodedMessage import org.xmtp.android.library.messages.Attachment import org.xmtp.android.library.messages.ContentTypeAttachment import org.xmtp.android.library.messages.SendOptions import kotlinx.coroutines.launch // Assuming 'dm' is an instance of a DM conversation and 'client' is an initialized XMTP client instance // Assuming 'lifecycleScope' is available for coroutine launching // Assuming 'fileBytes' is a ByteArray containing the attachment data // Assuming 'lastReadTimestamp' is a Long representing the last read message timestamp in nanoseconds // Send text message in DM val messageId = dm.send("Hey! How's it going?") // Send custom content val attachment = Attachment( filename = "document.pdf", mimeType = "application/pdf", data = fileBytes ) dm.send( content = attachment, options = SendOptions(contentType = ContentTypeAttachment) ) // Get DM message history val dmMessages = dm.messages( limit = 100, direction = DecodedMessage.SortDirection.DESCENDING ) dmMessages.forEach { message -> if (message.senderInboxId == client.inboxId) { println("You: ${message.body}") } else { println("${dm.peerInboxId}: ${message.body}") } } // Stream DM messages in real-time lifecycleScope.launch { dm.streamMessages().collect { message -> if (message.senderInboxId != client.inboxId) { // New message from peer showNotification(dm.peerInboxId, message.body) // Placeholder function updateChatUI(message) // Placeholder function } } } // Count unread messages val unreadCount = dm.countMessages( afterNs = lastReadTimestamp, deliveryStatus = DecodedMessage.MessageDeliveryStatus.PUBLISHED ) // Get DM members val members = dm.members() members.forEach { member -> println("Participant: ${member.inboxId}") } ``` -------------------------------- ### Local Database Management (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt Manage the local encrypted database used by the XMTP client to store conversations and messages. This includes accessing the database path, deleting the entire local database, and dropping/reconnecting the database connection. ```kotlin import org.xmtp.android.library.Client import org.xmtp.android.library.ClientOptions import org.xmtp.android.library.XMTPEnvironment import org.xmtp.android.library.extensions.generateSecureRandomBytes import android.content.Context import android.util.Log // Assuming 'client' is an initialized XMTP client instance // Database is automatically created and managed println("Database path: ${client.dbPath}") // Delete local database (caution: deletes all local data) try { client.deleteLocalDatabase() println("Local database deleted") // Client instance is now invalid, create new client } catch (e: Exception) { Log.e("XMTP", "Failed to delete database: ${e.message}", e) } // Drop database connection (advanced usage) client.dropLocalDatabaseConnection() // App will error if database operations attempted // Must reconnect before using client client.reconnectLocalDatabase() // Custom database directory during client creation // Replace with actual Context and signingKey // val applicationContext: Context = ... // val signingKey: ByteArray = ... // val encryptionKey: ByteArray = generateSecureRandomBytes(32) // val customOptions = ClientOptions( // api = ClientOptions.Api(env = XMTPEnvironment.PRODUCTION), // appContext = applicationContext, // dbEncryptionKey = encryptionKey, // dbDirectory = "${applicationContext.filesDir}/custom_xmtp_db" // ) // val clientWithCustomDb = Client.create(signingKey, customOptions) // Database is encrypted with provided key // Store encryption key securely (Android KeyStore recommended) // val encryptionKey = generateSecureRandomBytes(32) // AndroidKeyStore.storeKey("xmtp_db_key", encryptionKey) ``` -------------------------------- ### Create Group Conversation (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt Enables the creation of new group conversations with specified members, permissions, and metadata. Supports setting group names, images, descriptions, and disappearing message settings. Members can be added using inbox IDs or PublicIdentity objects. ```kotlin import org.xmtp.android.library.libxmtp.GroupPermissionPreconfiguration import org.xmtp.android.library.libxmtp.DisappearingMessageSettings // Create group with inbox IDs val memberInboxIds = listOf( "inbox_id_1", "inbox_id_2", "inbox_id_3" ) val group = try { client.conversations.newGroup( inboxIds = memberInboxIds, permissions = GroupPermissionPreconfiguration.ALL_MEMBERS, // or ADMIN_ONLY groupName = "Product Launch Team", groupImageUrlSquare = "https://example.com/avatar.png", groupDescription = "Coordination for Q4 product launch", disappearingMessageSettings = DisappearingMessageSettings( disappearStartingAtNs = System.currentTimeMillis() * 1_000_000, retentionDurationInNs = 86400_000_000_000L // 24 hours ), appData = "{\"customField\": \"value\"}" // Optional JSON metadata ) println("Group created: ${group.id}") println("Group name: ${group.name()}") println("Members: ${group.members().size}") group } catch (e: Exception) { Log.e("XMTP", "Failed to create group: ${e.message}", e) throw e } // Alternative: create with PublicIdentity objects val memberIdentities = listOf( PublicIdentity(IdentityKind.ETHEREUM, "0xAddress1..."), PublicIdentity(IdentityKind.ETHEREUM, "0xAddress2...") ) val groupByIdentity = client.conversations.newGroupWithIdentities( identities = memberIdentities, permissions = GroupPermissionPreconfiguration.ALL_MEMBERS, groupName = "Team Chat" ) ``` -------------------------------- ### Register and Use Custom Content Types in XMTP Android Source: https://context7.com/xmtp/xmtp-android/llms.txt Demonstrates how to define, implement, register, and use custom content types like 'Poll' with the XMTP Android library. It covers encoding, decoding, and sending/receiving messages with custom payloads. ```kotlin import org.xmtp.android.library.codecs.ContentCodec import org.xmtp.android.library.codecs.ContentTypeId import org.xmtp.android.library.codecs.EncodedContent import com.google.protobuf.kotlin.toByteString // Define custom content type data class Poll( val question: String, val options: List, val multipleChoice: Boolean = false ) // Implement codec class PollCodec : ContentCodec { override val contentType = ContentTypeId( authorityId = "myapp.com", typeId = "poll", versionMajor = 1, versionMinor = 0 ) override fun encode(content: Poll): EncodedContent { val json = """ { "question": "${content.question}", "options": ${content.options.joinToString(",", "[", "]") { "\"$it\"" }}, "multipleChoice": ${content.multipleChoice} } """.trimIndent() return EncodedContent.newBuilder() .setType(contentType) .setContent(json.toByteString()) .build() } override fun decode(content: EncodedContent): Poll { val json = content.content.toStringUtf8() // Parse JSON (use your preferred JSON library) return parsePollFromJson(json) } override fun fallback(content: Poll): String { return "Poll: ${content.question}" } override fun shouldPush(content: Poll): Boolean = true } // Register codec before creating client Client.register(PollCodec()) // Also register built-in codecs as needed Client.register(ReactionCodec()) Client.register(ReadReceiptCodec()) Client.register(ReplyCodec()) Client.register(AttachmentCodec()) // Send message with custom content type val poll = Poll( question = "What time works for the meeting?", options = listOf("9 AM", "11 AM", "2 PM", "4 PM"), multipleChoice = false ) conversation.send( content = poll, options = SendOptions(contentType = PollCodec().contentType) ) // Receive and decode val messages = conversation.messages(limit = 10) messages.forEach { message -> when (message.encodedContent.type.typeId) { "poll" -> { val poll = message.content() poll?.let { println("Poll: ${it.question}") it.options.forEach { option -> println(" - $option") } } } "text" -> { println("Text: ${message.body}") } } } ``` -------------------------------- ### Sync Conversations in Kotlin Source: https://context7.com/xmtp/xmtp-android/llms.txt Synchronize conversation state with the network. This includes lightweight metadata syncing, full message history synchronization, and syncing specific conversations. Periodic background syncing is also supported to maintain up-to-date conversation data. ```kotlin // Sync conversation list (lightweight - only metadata) try { client.conversations.sync() println("Conversation list synced") } catch (e: Exception) { Log.e("XMTP", "Sync failed: ${e.message}", e) } // Sync all conversations with full message history val syncSummary = client.conversations.syncAllConversations( consentStates = listOf(ConsentState.ALLOWED) ) println("Synced ${syncSummary.numSynced} of ${syncSummary.numEligible} conversations") // Sync specific conversation val conversation = client.conversations.findGroup("group_id") conversation?.sync() // Periodic background sync lifecycleScope.launch { while (isActive) { delay(300_000) // 5 minutes try { client.conversations.syncAllConversations() } catch (e: Exception) { Log.w("XMTP", "Background sync failed: ${e.message}") } } } ``` -------------------------------- ### Registering for Push Notifications (Kotlin) Source: https://github.com/xmtp/xmtp-android/blob/main/library/src/main/java/org/xmtp/android/library/push/README.md This Kotlin code snippet demonstrates how to register the device's FCM token with the XMTP push notification service. It requires an initialized context and the notification server address. ```kotlin XMTPPush(context, "10.0.2.2:8080").register(token) ``` -------------------------------- ### List and Filter Conversations in Kotlin Source: https://context7.com/xmtp/xmtp-android/llms.txt Retrieve and filter conversations (groups and DMs) based on various criteria such as consent state, creation time, and last activity. It also shows how to find specific conversations by ID or topic. ```kotlin import org.xmtp.android.library.Conversations // List all conversations (groups + DMs) val allConversations = client.conversations.list( limit = 50, orderBy = Conversations.ListConversationsOrderBy.LAST_ACTIVITY, consentStates = listOf(ConsentState.ALLOWED) ) allConversations.forEach { conversation -> when (conversation) { is Conversation.Group -> { println("Group: ${conversation.group.name()}") println(" Members: ${conversation.group.members().size}") } is Conversation.Dm -> { println("DM with: ${conversation.dm.peerInboxId}") } } println(" Last activity: ${conversation.lastActivityNs}") } // List only groups val groups = client.conversations.listGroups( createdAfterNs = sevenDaysAgo, orderBy = Conversations.ListConversationsOrderBy.CREATED_AT ) // List only DMs val dms = client.conversations.listDms( lastActivityAfterNs = oneDayAgo, limit = 20 ) // Filter by time range val recentConversations = client.conversations.list( createdAfterNs = startTimeNs, createdBeforeNs = endTimeNs, lastActivityAfterNs = lastWeekNs ) // Find specific conversation val conversation = client.conversations.findConversation("conversation_id_hex") conversation?.let { println("Found: ${it.id}") println("Type: ${it.type}") // GROUP or DM } // Find by topic (for advanced use) val conversationByTopic = client.conversations.findConversationByTopic( "/xmtp/mls/1/g-conversation_id/proto" ) ``` -------------------------------- ### Update XMTP Group Metadata (Kotlin) Source: https://context7.com/xmtp/xmtp-android/llms.txt This snippet demonstrates how to update various metadata associated with an XMTP group, including its name, description, image URL, app-specific data, and disappearing message settings. It also shows how to manage permissions for adding/removing members and updating the group name, as well as how to read the current disappearing message settings. ```kotlin import org.xmtp.android.library.exceptions.XMTPException import org.xmtp.android.library.models.DisappearingMessageSettings import org.xmtp.android.library.models.PermissionOption import android.util.Log // Assuming 'group' is an instance of an XMTP group object // Update group name try { group.updateName("Product Launch 2024") println("Group name updated") } catch (e: XMTPException) { Log.e("XMTP", "Permission denied: ${e.message}") } // Update group image URL group.updateImageUrl("https://example.com/new-avatar.png") // Update group description group.updateDescription("New product launch coordination - Q4 2024") // Update app-specific data val appMetadata = """ { "category": "work", "priority": "high", "customTags": ["launch", "q4"] } """.trimIndent() group.updateAppData(appMetadata) // Configure disappearing messages val newDisappearSettings = DisappearingMessageSettings( disappearStartingAtNs = System.currentTimeMillis() * 1_000_000, retentionDurationInNs = 3600_000_000_000L // 1 hour ) group.updateDisappearingMessageSettings(newDisappearSettings) // Disable disappearing messages group.clearDisappearingMessageSettings() // Update permissions group.updateAddMemberPermission(PermissionOption.ADMIN_ONLY) group.updateRemoveMemberPermission(PermissionOption.SUPER_ADMIN_ONLY) group.updateNamePermission(PermissionOption.ADMIN_ONLY) // Read settings val settings = group.disappearingMessageSettings() settings?.let { println("Messages disappear after: ${it.retentionDurationInNs / 1_000_000_000} seconds") } ``` -------------------------------- ### Stream Group Messages in Real-Time with XMTP Android Source: https://context7.com/xmtp/xmtp-android/llms.txt Enables real-time subscription to new messages arriving in XMTP group conversations. This functionality allows developers to process incoming messages as they are received, update the user interface dynamically, and handle message streams for individual groups or all groups the client is part of. It includes options for managing stream closures and filtering messages based on conversation type and consent state. ```kotlin import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.xmtp.android.library.Conversations import org.xmtp.android.library.Conversation import org.xmtp.android.library.ConsentState import android.util.Log // Stream messages from specific group lifecycleScope.launch { group.streamMessages( onClose = { Log.d("XMTP", "Message stream closed") } ).collect { // Handle incoming message in real-time println("New message from ${it.senderInboxId}: ${it.body}") // Update UI withContext(Dispatchers.Main) { messageAdapter.addMessage(it) recyclerView.smoothScrollToPosition(0) } } } // Stream all messages from all groups lifecycleScope.launch { client.conversations.streamAllMessages( type = Conversations.ConversationFilterType.GROUPS, consentStates = listOf(ConsentState.ALLOWED) ).collect { // Find conversation for this message val conversation = client.conversations.findConversation(it.conversationId) when (conversation) { is Conversation.Group -> { println("Group message in: ${conversation.group.name()}") notifyUser("${conversation.group.name()}: ${it.body}") } else -> {} } } } ```