Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ android {
kotlinOptions {
jvmTarget = "17"
}

testOptions {
unitTests.all {
useJUnit()
}
}
}

repositories {
Expand All @@ -81,6 +87,7 @@ dependencies {
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.frontegg.sdk:android:1.3.34'
testImplementation "junit:junit:4.13.2"
}

if (isNewArchitectureEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.frontegg.reactnative

import android.content.Context
import android.content.pm.PackageManager
import android.util.Log

/** Manifest meta-data name for the package that contains the host app [BuildConfig]. */
const val BUILD_CONFIG_PACKAGE_META_DATA_NAME = "com.frontegg.reactnative.BUILD_CONFIG_PACKAGE"

/**
* Ordered FQCN candidates for the host app [BuildConfig] class.
*
* @param applicationId Runtime application id ([Context.getPackageName]).
* @param manifestBuildConfigPackage Optional value from [BUILD_CONFIG_PACKAGE_META_DATA_NAME].
*/
fun buildConfigClassCandidates(
applicationId: String,
manifestBuildConfigPackage: String?,
): List<String> {
val candidates = mutableListOf("$applicationId.BuildConfig")

val packageName = manifestBuildConfigPackage?.trim()?.takeIf { it.isNotEmpty() } ?: return candidates

val className =
if (packageName.endsWith(".BuildConfig")) packageName else "$packageName.BuildConfig"

if (className !in candidates) {
candidates.add(className)
}

return candidates
}

fun resolveBuildConfigClass(
applicationId: String,
manifestBuildConfigPackage: String?,
): Class<*> {
val candidates = buildConfigClassCandidates(applicationId, manifestBuildConfigPackage)
var lastError: ClassNotFoundException? = null

for (className in candidates) {
try {
return Class.forName(className)
} catch (e: ClassNotFoundException) {
Log.w(TAG, "BuildConfig not found at $className, trying next candidate")
lastError = e
}
}

val hint =
if (manifestBuildConfigPackage.isNullOrBlank()) {
"If applicationId differs from your AGP namespace, add <meta-data " +
"android:name=\"$BUILD_CONFIG_PACKAGE_META_DATA_NAME\" " +
"android:value=\"your.namespace\" /> under <application>."
} else {
"Verify <meta-data android:name=\"$BUILD_CONFIG_PACKAGE_META_DATA_NAME\" /> " +
"points to the package that contains BuildConfig."
}

Log.e(TAG, "Could not load BuildConfig. Tried: ${candidates.joinToString()}. $hint")

throw lastError ?: ClassNotFoundException(
"No BuildConfig class found for applicationId=$applicationId"
)
}

fun Context.readBuildConfigPackageMetaData(): String? {
return try {
val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
appInfo.metaData?.getString(BUILD_CONFIG_PACKAGE_META_DATA_NAME)
} catch (e: Exception) {
Log.w(TAG, "Failed to read $BUILD_CONFIG_PACKAGE_META_DATA_NAME from manifest", e)
null
}
}
7 changes: 4 additions & 3 deletions android/src/main/java/com/frontegg/reactnative/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ interface ActivityProvider {
val Context.fronteggConstants: FronteggConstants
get() {
val packageName = this.packageName
val className = "$packageName.BuildConfig"
try {
val buildConfigClass = Class.forName(className)
val buildConfigClass = resolveBuildConfigClass(
packageName,
readBuildConfigPackageMetaData(),
)

// Get the field from BuildConfig class
val baseUrl = safeGetValueFromBuildConfig(buildConfigClass, "FRONTEGG_DOMAIN", "")
Expand All @@ -41,7 +43,6 @@ val Context.fronteggConstants: FronteggConstants
bundleId = this.packageName,
)
} catch (e: ClassNotFoundException) {
Log.e(TAG, "Class not found: $className")
throw e
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.frontegg.reactnative

import org.junit.Assert.assertEquals
import org.junit.Test

class BuildConfigResolverTest {

@Test
fun buildConfigClassCandidates_usesApplicationIdWhenNoMetaData() {
assertEquals(
listOf("com.example.app.BuildConfig"),
buildConfigClassCandidates("com.example.app", null),
)
}

@Test
fun buildConfigClassCandidates_appendsMetaDataPackage() {
assertEquals(
listOf(
"com.healthie.app.flavor.BuildConfig",
"com.main.BuildConfig",
),
buildConfigClassCandidates("com.healthie.app.flavor", "com.main"),
)
}

@Test
fun buildConfigClassCandidates_acceptsFullyQualifiedBuildConfigInMetaData() {
assertEquals(
listOf("com.example.app.BuildConfig", "com.main.BuildConfig"),
buildConfigClassCandidates("com.example.app", "com.main.BuildConfig"),
)
}

@Test
fun buildConfigClassCandidates_doesNotDuplicateWhenMetaDataMatchesApplicationId() {
assertEquals(
listOf("com.example.app.BuildConfig"),
buildConfigClassCandidates("com.example.app", "com.example.app"),
)
}

@Test
fun buildConfigClassCandidates_ignoresBlankMetaData() {
assertEquals(
listOf("com.example.app.BuildConfig"),
buildConfigClassCandidates("com.example.app", " "),
)
}
}
12 changes: 12 additions & 0 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,18 @@ android {
}
```

### White-label apps (`applicationId` ≠ `namespace`)

If your product flavors use different `applicationId` values but share one Java/Kotlin `namespace`, AGP generates `BuildConfig` under the **namespace** package, not under each flavor's `applicationId`. The SDK looks up `BuildConfig` by `applicationId` first; when that fails, add this under `<application>` in `android/app/src/main/AndroidManifest.xml` (use your shared namespace, without `.BuildConfig`):

```xml
<meta-data
android:name="com.frontegg.reactnative.BUILD_CONFIG_PACKAGE"
android:value="com.main" />
```

Apps where `applicationId` matches `namespace` do not need this entry.

### Enable Android AssetLinks

To enable Android features like Magic Link authentication, password reset, account activation, and login with identity providers, follow the steps below.
Expand Down
Loading