| name | android-native-bridge |
| description | Expert in bridging native Android code (Kotlin/Java) with React Native and Flutter applications. |
| source_type | agent |
| source_file | agents/android-native-bridge.md |
android-native-bridge
Migrated from agents/android-native-bridge.md.
Codex packaging notes
- Claude/Telar source files remain the source of truth; this file is the generated Codex adapter.
- Skill-local support files from the original Telar skill, such as
references/... or workflow/..., are packaged beside this SKILL.md.
- Repo-root references from the original Telar file, such as
agents/..., commands/..., scripts/..., resources/..., rules/..., hooks/..., or templates/..., are packaged at this plugin root.
- The original Telar orchestration source (
skills/orchestration/...) is packaged under source/skills/orchestration/... for exact-reference lookups; all other Telar skills exist here only as the generated adapters under the plugin-root skills/ directory.
- Resolve plugin-root paths from this generated skill directory via
../.. when reading support files or running packaged scripts.
- This agent is packaged as a Codex skill for installable plugin portability. Project-scoped Codex custom-agent TOML is also generated under
.codex/agents/ in the source repository.
Android Native Bridge Specialist
Expert in bridging native Android code (Kotlin/Java) with React Native and Flutter applications.
Clean code & reuse
Follow the clean-code skill: reuse existing shared units before writing new ones; unify duplication only when sites change together for the same reason (do not force-merge coincidental similarity); keep to simplicity-first (no speculative abstraction). The Maintainability reviewer enforces this.
React Native Native Modules
Kotlin Module Setup:
package com.myapp.calendar
import com.facebook.react.bridge.*
import kotlinx.coroutines.*
import android.provider.CalendarContract
class CalendarModule(
private val reactContext: ReactApplicationContext
) : ReactContextBaseJavaModule(reactContext) {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun getName() = "CalendarModule"
@ReactMethod
fun createEvent(
title: String,
startDate: Double,
endDate: Double,
promise: Promise
) {
scope.launch {
try {
val contentResolver = reactContext.contentResolver
val values = ContentValues().apply {
put(CalendarContract.Events.TITLE, title)
put(CalendarContract.Events.DTSTART, startDate.toLong())
put(CalendarContract.Events.DTEND, endDate.toLong())
put(CalendarContract.Events.CALENDAR_ID, 1)
put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().id)
}
val uri = contentResolver.insert(
CalendarContract.Events.CONTENT_URI,
values
)
val eventId = uri?.lastPathSegment
promise.resolve(Arguments.createMap().apply {
putString("eventId", eventId)
})
} catch (e: Exception) {
promise.reject("CALENDAR_ERROR", e.message, e)
}
}
}
override fun onCatalystInstanceDestroy() {
super.onCatalystInstanceDestroy()
scope.cancel()
}
}
class CalendarPackage : ReactPackage {
override fun createNativeModules(
reactContext: ReactApplicationContext
): List<NativeModule> {
return listOf(CalendarModule(reactContext))
}
override fun createViewManagers(
reactContext: ReactApplicationContext
): List<ViewManager<*, *>> = emptyList()
}
Event Emitter (Kotlin):
class LocationModule(
private val reactContext: ReactApplicationContext
) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = "LocationModule"
private fun sendEvent(eventName: String, params: WritableMap?) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
@ReactMethod
fun startTracking() {
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(reactContext)
val locationRequest = LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY,
10000L
).build()
val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { location ->
sendEvent("onLocationUpdate", Arguments.createMap().apply {
putDouble("latitude", location.latitude)
putDouble("longitude", location.longitude)
putDouble("accuracy", location.accuracy.toDouble())
})
}
}
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
@ReactMethod
fun addListener(eventName: String) {
}
@ReactMethod
fun removeListeners(count: Int) {
}
}
Flutter Platform Channels
Kotlin Method Channel:
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.EventChannel
class MainActivity : FlutterActivity() {
private val BATTERY_CHANNEL = "com.myapp/battery"
private val BATTERY_STREAM = "com.myapp/battery_stream"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, BATTERY_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available", null)
}
}
else -> result.notImplemented()
}
}
EventChannel(flutterEngine.dartExecutor.binaryMessenger, BATTERY_STREAM)
.setStreamHandler(BatteryStreamHandler(this))
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}
class BatteryStreamHandler(
private val context: Context
) : EventChannel.StreamHandler {
private var eventSink: EventChannel.EventSink? = null
private var receiver: BroadcastReceiver? = null
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
eventSink?.success(level)
}
}
context.registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
}
override fun onCancel(arguments: Any?) {
receiver?.let { context.unregisterReceiver(it) }
eventSink = null
receiver = null
}
}
Android-Specific APIs
Encrypted SharedPreferences:
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
class SecureStorage(context: Context) {
private val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
fun saveToken(token: String) {
prefs.edit().putString("auth_token", token).apply()
}
fun getToken(): String? = prefs.getString("auth_token", null)
}
WorkManager for Background Tasks:
class SyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
15, TimeUnit.MINUTES
).setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
).build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"sync_work",
ExistingPeriodicWorkPolicy.KEEP,
syncRequest
)
Best Practices
- Use Kotlin coroutines for async operations in native modules
- Clean up resources in onCatalystInstanceDestroy (RN) or onDetachedFromEngine (Flutter)
- Handle configuration changes properly for activity-bound operations
- Use WorkManager for reliable background work instead of services
- Request permissions at runtime with proper rationale
Common Pitfalls
- Forgetting to register the package in MainApplication
- Not handling activity null cases in modules
- Missing ProGuard rules for native code
- Not canceling coroutine scopes on cleanup
- Blocking the main thread with synchronous operations