---
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