Receiving messages
A guide to integrate RNC message reception into your Android app: both command responses and asynchronous notifications (guidance progress, alerts, …) sent by BeNav (com.benomad.benav).
This covers the receive path only.
How BeNav returns RNC messages#
BeNav emits every RNC message as a global broadcast.
There is a single channel for both responses and notifications:
| Intent action | com.benomad.ACTION_RNC |
| Extra key | message (String) |
| Payload | the JSON-RPC message (response or notification) |
| Broadcast type | global sendBroadcast(), no target package or permission |
Because BeNav does not track who sent a command, responses are not routed back to a specific caller; they are broadcast to everyone listening.
To tell which response matches which command, correlate the JSON-RPC id field with the id you sent.
Integration#
1. Register a runtime receiver (not a manifest one)#
The broadcast is implicit (an action with no target package).
Because the sender (BeNav) is a different app, the receiver must be registered as exported.
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.core.content.ContextCompat
object RncResponseListener {
private const val ACTION = "com.benomad.ACTION_RNC"
private const val EXTRA_MESSAGE = "message"
/** Registers a receiver that forwards each RNC message (JSON String) to [onMessage]. */
fun register(context: Context, onMessage: (String) -> Unit): BroadcastReceiver {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
intent.getStringExtra(EXTRA_MESSAGE)?.let(onMessage)
}
}
ContextCompat.registerReceiver(
context,
receiver,
IntentFilter(ACTION),
ContextCompat.RECEIVER_EXPORTED,
)
return receiver
}
fun unregister(context: Context, receiver: BroadcastReceiver) {
runCatching { context.unregisterReceiver(receiver) }
}
}It needs androidx.core:core-ktx (for ContextCompat).
No manifest entry and no permission are required.
2. Tie registration to a lifecycle#
Register while you want to receive, and unregister when you don't.
The right scope depends on your needs:
- Register for the lifetime of an
Activity/Service, or - Register only while a screen is visible.
Registering for as long as the component lives means messages keep arriving even while BeNav is in the foreground and your app is in the background (your process stays alive).
Plain Activity:
class YourActivity : ComponentActivity() {
private var receiver: BroadcastReceiver? = null
override fun onStart() {
super.onStart()
receiver = RncResponseListener.register(this) { message ->
handleRncMessage(message)
}
}
override fun onStop() {
receiver?.let { RncResponseListener.unregister(this, it) }
receiver = null
super.onStop()
}
}Jetpack Compose:
val context = LocalContext.current
DisposableEffect(Unit) {
val receiver = RncResponseListener.register(context) { message ->
handleRncMessage(message)
}
onDispose { RncResponseListener.unregister(context, receiver) }
}
onReceiveruns on the main thread.
KeeponMessagelight (or hand the JSON off to your own dispatcher/coroutine); don't do blocking work there.
3. Handle the JSON#
message is the raw JSON-RPC string.
Parse it with your JSON library of choice and route by shape:
- A response has a
result(success) or anerror(failure) and anidthat matches the command you sent; use thatidto correlate. - A notification has no matching command
id; dispatch by itsmethod.
fun handleRncMessage(json: String) {
// e.g. parse, then branch on "id" / "result" / "error" / "method"
}