---
title: Integration Guide - Beeper Developer Docs
description: Permissions, notifications, troubleshooting, and best practices for Beeper Content Provider integration
---

## Permissions

### Required permissions

Beeper uses custom Android permissions that must be declared in your app’s manifest:

- [Manifest Declaration](#tab-panel-53)
- [Permission Details](#tab-panel-54)
- [Checking & Requesting](#tab-panel-55)

AndroidManifest.xml

```
<uses-permission android:name="com.beeper.android.permission.READ_PERMISSION" />
<uses-permission android:name="com.beeper.android.permission.SEND_PERMISSION" />
```

| 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-48)
- [Current support](#tab-panel-49)

```
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<Application>()
                .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-50)
- [Technical](#tab-panel-51)
- [Performance](#tab-panel-52)

* **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 <info@beeper.com> for integration support
