--- title: Integration Guide - Beeper Developer Docs description: Permissions, notifications, troubleshooting, and best practices for Beeper Content Provider integration lastUpdated: 2026-02-23T00:29:17.000Z --- ## Permissions ### Required permissions Beeper uses custom Android permissions that must be declared in your app’s manifest: - [Manifest Declaration](#tab-panel-42) - [Permission Details](#tab-panel-43) - [Checking & Requesting](#tab-panel-44) AndroidManifest.xml ``` ``` | Permission | Type | Purpose | | ----------------- | ------- | ------------------------------- | | `READ_PERMISSION` | Runtime | Query chats, messages, contacts | | `SEND_PERMISSION` | Runtime | Send messages | These are runtime permissions. Declare them in the manifest and request them via an in-app permission prompt. ``` // Check and request at runtime val hasRead = ContextCompat.checkSelfPermission( this, "com.beeper.android.permission.READ_PERMISSION" ) == PackageManager.PERMISSION_GRANTED val hasSend = ContextCompat.checkSelfPermission( this, "com.beeper.android.permission.SEND_PERMISSION" ) == PackageManager.PERMISSION_GRANTED if (!hasRead || !hasSend) { val launcher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { results -> val grantedRead = results["com.beeper.android.permission.READ_PERMISSION"] == true val grantedSend = results["com.beeper.android.permission.SEND_PERMISSION"] == true // Handle granted/denied states } launcher.launch(arrayOf( "com.beeper.android.permission.READ_PERMISSION", "com.beeper.android.permission.SEND_PERMISSION" )) } ``` Users can deny permissions; handle gracefully and explain limited functionality if not granted. See Android’s [uses-permission](https://developer.android.com/guide/topics/manifest/uses-permission-element) and [permission](https://developer.android.com/guide/topics/manifest/permission-element) documentation. ## Change notifications ### ContentObserver setup Use `ContentObserver` to react to data changes in real-time: 1. **Create an observer** ``` class ChatObserver(handler: Handler) : ContentObserver(handler) { override fun onChange(selfChange: Boolean) { // Data changed - refresh your UI refreshChatList() } } ``` 2. **Register the observer** ``` import androidx.core.net.toUri val observer = ChatObserver(Handler(Looper.getMainLooper())) contentResolver.registerContentObserver( "content://com.beeper.api/chats".toUri(), true, // Notify for descendant URIs observer ) ``` 3. **Unregister when done** ``` override fun onDestroy() { super.onDestroy() contentResolver.unregisterContentObserver(observer) } ``` ### Common observer patterns - [Lifecycle-Aware](#tab-panel-37) - [Current support](#tab-panel-38) ``` class ChatViewModel : ViewModel() { private var observer: ContentObserver? = null fun startObserving(contentResolver: ContentResolver) { observer = object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean) { loadChats() } } contentResolver.registerContentObserver( "content://com.beeper.api/chats".toUri(), true, observer!! ) } override fun onCleared() { observer?.let { getApplication() .contentResolver .unregisterContentObserver(it) } } } ``` Only the `content://com.beeper.api/chats` URI currently emits change notifications. Observers on `messages` and `contacts` are not implemented yet. Some Cursor implementations auto-update, but explicit observers ensure consistent behavior across Android versions. ## Troubleshooting ### Common issues and solutions #### No rows returned **Causes:** - Beeper not installed/logged in - Missing permissions - Too restrictive filters **Solutions:** - Verify Beeper installation - Check manifest permissions - Add `limit` parameter - Adjust filters #### Permission denied **Causes:** - Permissions not declared in manifest - Runtime permissions not granted **Solutions:** - Add permissions to manifest - Request permissions at runtime #### Message send fails **Causes:** - Invalid room ID - Text not URL-encoded - Network issues **Solutions:** - Verify `roomId` format - Use `Uri.encode()` for text - Check Beeper connectivity #### UI not updating **Causes:** - No ContentObserver registered - Wrong Handler/Looper - Observer not on main thread **Solutions:** - Register ContentObserver - Use `Handler(Looper.getMainLooper())` - Refresh on main thread ### Debug checklist 1. **Verify Beeper installation** ``` val packageManager = context.packageManager try { packageManager.getPackageInfo("com.beeper.android", 0) // Beeper is installed } catch (e: PackageManager.NameNotFoundException) { // Beeper not installed } ``` 2. **Check permissions** ``` val permissions = listOf( "com.beeper.android.permission.READ_PERMISSION", "com.beeper.android.permission.SEND_PERMISSION" ) permissions.forEach { permission -> val granted = checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED Log.d("Permissions", "$permission: $granted") } ``` 3. **Test basic query** ``` try { val cursor = contentResolver.query( "content://com.beeper.api/chats?limit=1".toUri(), null, null, null, null ) Log.d("Debug", "Query returned ${cursor?.count ?: 0} rows") cursor?.close() } catch (e: Exception) { Log.e("Debug", "Query failed", e) } ``` ## Frequently asked questions - [General](#tab-panel-39) - [Technical](#tab-panel-40) - [Performance](#tab-panel-41) * **What’s the authority?** `com.beeper.api` * **Do I need runtime permissions?** Yes. Request `READ_PERMISSION` and `SEND_PERMISSION` at runtime in addition to declaring them in the manifest. * **What’s the default limit?** Messages and contacts default to 100 if not specified. Always specify `limit` for chats to avoid large queries. * **Can I use this from a service?** Yes, content providers work from any Android component with a Context. - **How do I open at the first unread message?** Use `openAtUnread=true` with exactly one `roomId` and no other filters. - **Can I send media messages?** Currently only text messages are supported via the content provider API. - **Is pagination cursor-based or offset-based?** Offset-based. Use `limit` and `offset` parameters. - **Are queries case-sensitive?** Text searches are case-insensitive. Room/sender IDs are case-sensitive. * **What’s the maximum limit?** No hard limit, but use reasonable values (50-200) for UI responsiveness. * **Can I cache query results?** Yes, but use ContentObserver to invalidate cache on changes. * **How often do observers trigger?** Immediately on data change. Consider debouncing for high-frequency updates. * **Should I close Cursors?** Always. Use try-with-resources or Kotlin’s `.use{}` extension. ## Best practices ### Always URL encode ``` // ✅ Good Uri.encode("Hello & welcome!") // ❌ Bad "Hello & welcome!" ``` ### Use lifecycle components ``` // Register in onStart override fun onStart() { super.onStart() registerObserver() } // Unregister in onStop override fun onStop() { super.onStop() unregisterObserver() } ``` ### Handle null results ``` // Always check for null val result = contentResolver.insert(uri, null) if (result != null) { // Success } else { // Handle failure } ``` ### Batch operations ``` // ✅ Single query with multiple IDs "roomIds=!room1:server,!room2:server" // ❌ Multiple queries query("roomIds=!room1:server") query("roomIds=!room2:server") ``` ## Testing Tips Development testing 1. **Use Android Studio’s Database Inspector** to view ContentProvider data 2. **Test with different Beeper account states** (logged out, no chats, many chats) 3. **Verify permission handling** by testing without declaring permissions 4. **Test observer behavior** by sending messages from another device 5. **Check edge cases** like empty results, special characters, long text ## Need help? - Check the [API Reference](/android/content-providers/api-reference/index.md) for detailed endpoint documentation - Review the [Quick start](/android/content-providers#quick-start/index.md) for basic examples - Contact for integration support