Integration Guide
Permissions
Section titled “Permissions”Required Permissions
Section titled “Required Permissions”Beeper uses custom Android permissions that must be declared in your app’s manifest:
<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 |
// Check and request at runtimeval 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" ))}
Change Notifications
Section titled “Change Notifications”ContentObserver Setup
Section titled “ContentObserver Setup”Use ContentObserver
to react to data changes in real-time:
-
Create an observer
class ChatObserver(handler: Handler) : ContentObserver(handler) {override fun onChange(selfChange: Boolean) {// Data changed - refresh your UIrefreshChatList()}} -
Register the observer
import androidx.core.net.toUrival observer = ChatObserver(Handler(Looper.getMainLooper()))contentResolver.registerContentObserver("content://com.beeper.api/chats".toUri(),true, // Notify for descendant URIsobserver) -
Unregister when done
override fun onDestroy() {super.onDestroy()contentResolver.unregisterContentObserver(observer)}
Common Observer Patterns
Section titled “Common Observer Patterns”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.
Troubleshooting
Section titled “Troubleshooting”Common Issues & Solutions
Section titled “Common Issues & Solutions”No rows returned
Section titled “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
Section titled “Permission denied”Causes:
- Permissions not declared in manifest
- Runtime permissions not granted
Solutions:
- Add permissions to manifest
- Request permissions at runtime
Message send fails
Section titled “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
Section titled “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
Section titled “Debug Checklist”-
Verify Beeper installation
val packageManager = context.packageManagertry {packageManager.getPackageInfo("com.beeper.android", 0)// Beeper is installed} catch (e: PackageManager.NameNotFoundException) {// Beeper not installed} -
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_GRANTEDLog.d("Permissions", "$permission: $granted")} -
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
Section titled “Frequently Asked Questions”- What’s the authority?
com.beeper.api
- Do I need runtime permissions?
- Yes. Request
READ_PERMISSION
andSEND_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, ContentProviders work from any Android component with a Context.
- How do I open at the first unread message?
- Use
openAtUnread=true
with exactly oneroomId
and no other filters. - Can I send media messages?
- Currently only text messages are supported via the ContentProvider API.
- Is pagination cursor-based or offset-based?
- Offset-based. Use
limit
andoffset
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
Section titled “Best Practices”Always URL Encode
Section titled “Always URL Encode”// ✅ GoodUri.encode("Hello & welcome!")
// ❌ Bad"Hello & welcome!"
Use Lifecycle Components
Section titled “Use Lifecycle Components”// Register in onStartoverride fun onStart() { super.onStart() registerObserver()}
// Unregister in onStopoverride fun onStop() { super.onStop() unregisterObserver()}
Handle Null Results
Section titled “Handle Null Results”// Always check for nullval result = contentResolver.insert(uri, null)if (result != null) { // Success} else { // Handle failure}
Batch Operations
Section titled “Batch Operations”// ✅ Single query with multiple IDs"roomIds=!room1:server,!room2:server"
// ❌ Multiple queriesquery("roomIds=!room1:server")query("roomIds=!room2:server")
Testing Tips
Section titled “Testing Tips”Need Help?
Section titled “Need Help?”- Check the API Reference for detailed endpoint documentation
- Review the Quick Start for basic examples
- Contact info@beeper.com for integration support