I have created my first native module to be used in my Expo project. It is working fine on iOS so far. Now I am trying to implement for Android. Except I am running into issues compiling the app, I think because my build.gradle
file is not being updated to match the new Module.kt file:
package expo.modules.boundingbox
import android.graphics.Color
import android.graphics.RectF
import android.view.View
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import expo.modules.kotlin.types.Void
// Custom View for drawing the bounding box
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
class BoundingBoxModule : Module() {
// Store a reference to the current box view
private var currentBoxView: View? = null
override fun definition() = ModuleDefinition {
Name("BoundingBox")
Function("drawBoundingBox") { boundingBox: Map<String, Double>?, color: String? ->
// Log the received arguments
android.util.Log.d("BoundingBoxModule", "Received boundingBox: $boundingBox")
android.util.Log.d("BoundingBoxModule", "Received color: $color")
// Run on main thread
activity?.runOnUiThread {
// Remove any existing box view first
removeExistingBoxView()
// Only draw if both boundingBox and color are provided
if (boundingBox != null && color != null) {
drawBox(boundingBox, color)
}
}
}
}
private fun removeExistingBoxView() {
currentBoxView?.let { view ->
(view.parent as? ViewGroup)?.removeView(view)
currentBoxView = null
}
}
private fun drawBox(boundingBox: Map<String, Double>, color: String) {
val rootView = activity?.window?.decorView?.rootView ?: return
// Extract bounding box coordinates
val top = boundingBox["top"]?.toFloat() ?: return
val left = boundingBox["left"]?.toFloat() ?: return
val bottom = boundingBox["bottom"]?.toFloat() ?: return
val right = boundingBox["right"]?.toFloat() ?: return
// Calculate view dimensions
val viewWidth = rootView.width.toFloat()
val viewHeight = rootView.height.toFloat()
// Scale normalized coordinates to view dimensions
val rect = RectF(
left * viewWidth,
top * viewHeight,
right * viewWidth,
bottom * viewHeight
)
// Create a custom view for drawing the bounding box
val boxView = BoundingBoxView(
context = rootView.context,
rect = rect,
color = parseColor(color),
cornerRadius = 8f
)
// Position the view to cover the entire root view
val layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
boxView.layoutParams = layoutParams
// Add the view to the root view
(rootView as ViewGroup).addView(boxView)
currentBoxView = boxView
}
// Helper function to parse color strings
private fun parseColor(colorString: String): Int {
return try {
// Remove # if present
val cleanColor = colorString.trimStart('#')
// Handle different color format lengths
when (cleanColor.length) {
6 -> Color.parseColor("#$cleanColor")
8 -> Color.parseColor("#$cleanColor")
3 -> {
// Expand 3-digit hex to 6-digit
val expandedColor = cleanColor.map { "$it$it" }.joinToString("")
Color.parseColor("#$expandedColor")
}
else -> Color.BLACK
}
} catch (e: IllegalArgumentException) {
Color.BLACK
}
}
}
class BoundingBoxView(
context: Context,
private val rect: RectF,
private val color: Int,
private val cornerRadius: Float
) : View(context) {
private val paint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 4f
this.color = color
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
}
}
This is what I have put together based on the default config from Expo + help from ChatGPT, but I am not sure what else to do
apply plugin: 'com.android.library'
group = 'expo.modules.boundingbox'
version = '0.6.0'
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useExpoPublishing()
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
// Most of the time, you may like to manage the Android SDK versions yourself.
def useManagedAndroidSdkVersions = false
if (useManagedAndroidSdkVersions) {
useDefaultAndroidSdkVersions()
} else {
buildscript {
// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
dependencies {
// Expo modules core dependency
implementation "com.facebook.react:react-android"
implementation "expo.modules:core:${expo_modules_core_version}"
// Kotlin standard library
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlin_version}"
}
}
project.android {
compileSdkVersion safeExtGet("compileSdkVersion", 34)
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 34)
}
}
}
android {
namespace "expo.modules.boundingbox"
defaultConfig {
versionCode 1
versionName "0.6.0"
}
lintOptions {
abortOnError false
}
}
The error message is:
Could not find method implementation() for arguments [project ‘:expo-modules-core’] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
How do I modify the build.gradle to match my Module.kt file?