Skip to content

Commit 03a78d6

Browse files
mykyta-fronteggdianaKhortiuk-fronteggclaude
authored
Allow BuildConfig lookup when applicationId != namespace (#76)
Co-authored-by: dianaKhortiuk-frontegg <diana.khortiuk@frontegg.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e5bbfb1 commit 03a78d6

5 files changed

Lines changed: 148 additions & 3 deletions

File tree

android/build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ android {
6262
kotlinOptions {
6363
jvmTarget = "17"
6464
}
65+
66+
testOptions {
67+
unitTests.all {
68+
useJUnit()
69+
}
70+
}
6571
}
6672

6773
repositories {
@@ -81,6 +87,7 @@ dependencies {
8187
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'
8288
implementation 'com.google.code.gson:gson:2.10.1'
8389
implementation 'com.frontegg.sdk:android:1.3.34'
90+
testImplementation "junit:junit:4.13.2"
8491
}
8592

8693
if (isNewArchitectureEnabled()) {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.frontegg.reactnative
2+
3+
import android.content.Context
4+
import android.content.pm.PackageManager
5+
import android.util.Log
6+
7+
/** Manifest meta-data name for the package that contains the host app [BuildConfig]. */
8+
const val BUILD_CONFIG_PACKAGE_META_DATA_NAME = "com.frontegg.reactnative.BUILD_CONFIG_PACKAGE"
9+
10+
/**
11+
* Ordered FQCN candidates for the host app [BuildConfig] class.
12+
*
13+
* @param applicationId Runtime application id ([Context.getPackageName]).
14+
* @param manifestBuildConfigPackage Optional value from [BUILD_CONFIG_PACKAGE_META_DATA_NAME].
15+
*/
16+
fun buildConfigClassCandidates(
17+
applicationId: String,
18+
manifestBuildConfigPackage: String?,
19+
): List<String> {
20+
val candidates = mutableListOf("$applicationId.BuildConfig")
21+
22+
val packageName = manifestBuildConfigPackage?.trim()?.takeIf { it.isNotEmpty() } ?: return candidates
23+
24+
val className =
25+
if (packageName.endsWith(".BuildConfig")) packageName else "$packageName.BuildConfig"
26+
27+
if (className !in candidates) {
28+
candidates.add(className)
29+
}
30+
31+
return candidates
32+
}
33+
34+
fun resolveBuildConfigClass(
35+
applicationId: String,
36+
manifestBuildConfigPackage: String?,
37+
): Class<*> {
38+
val candidates = buildConfigClassCandidates(applicationId, manifestBuildConfigPackage)
39+
var lastError: ClassNotFoundException? = null
40+
41+
for (className in candidates) {
42+
try {
43+
return Class.forName(className)
44+
} catch (e: ClassNotFoundException) {
45+
Log.w(TAG, "BuildConfig not found at $className, trying next candidate")
46+
lastError = e
47+
}
48+
}
49+
50+
val hint =
51+
if (manifestBuildConfigPackage.isNullOrBlank()) {
52+
"If applicationId differs from your AGP namespace, add <meta-data " +
53+
"android:name=\"$BUILD_CONFIG_PACKAGE_META_DATA_NAME\" " +
54+
"android:value=\"your.namespace\" /> under <application>."
55+
} else {
56+
"Verify <meta-data android:name=\"$BUILD_CONFIG_PACKAGE_META_DATA_NAME\" /> " +
57+
"points to the package that contains BuildConfig."
58+
}
59+
60+
Log.e(TAG, "Could not load BuildConfig. Tried: ${candidates.joinToString()}. $hint")
61+
62+
throw lastError ?: ClassNotFoundException(
63+
"No BuildConfig class found for applicationId=$applicationId"
64+
)
65+
}
66+
67+
fun Context.readBuildConfigPackageMetaData(): String? {
68+
return try {
69+
val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
70+
appInfo.metaData?.getString(BUILD_CONFIG_PACKAGE_META_DATA_NAME)
71+
} catch (e: Exception) {
72+
Log.w(TAG, "Failed to read $BUILD_CONFIG_PACKAGE_META_DATA_NAME from manifest", e)
73+
null
74+
}
75+
}

android/src/main/java/com/frontegg/reactnative/Utils.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ interface ActivityProvider {
1515
val Context.fronteggConstants: FronteggConstants
1616
get() {
1717
val packageName = this.packageName
18-
val className = "$packageName.BuildConfig"
1918
try {
20-
val buildConfigClass = Class.forName(className)
19+
val buildConfigClass = resolveBuildConfigClass(
20+
packageName,
21+
readBuildConfigPackageMetaData(),
22+
)
2123

2224
// Get the field from BuildConfig class
2325
val baseUrl = safeGetValueFromBuildConfig(buildConfigClass, "FRONTEGG_DOMAIN", "")
@@ -41,7 +43,6 @@ val Context.fronteggConstants: FronteggConstants
4143
bundleId = this.packageName,
4244
)
4345
} catch (e: ClassNotFoundException) {
44-
Log.e(TAG, "Class not found: $className")
4546
throw e
4647
}
4748
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.frontegg.reactnative
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Test
5+
6+
class BuildConfigResolverTest {
7+
8+
@Test
9+
fun buildConfigClassCandidates_usesApplicationIdWhenNoMetaData() {
10+
assertEquals(
11+
listOf("com.example.app.BuildConfig"),
12+
buildConfigClassCandidates("com.example.app", null),
13+
)
14+
}
15+
16+
@Test
17+
fun buildConfigClassCandidates_appendsMetaDataPackage() {
18+
assertEquals(
19+
listOf(
20+
"com.healthie.app.flavor.BuildConfig",
21+
"com.main.BuildConfig",
22+
),
23+
buildConfigClassCandidates("com.healthie.app.flavor", "com.main"),
24+
)
25+
}
26+
27+
@Test
28+
fun buildConfigClassCandidates_acceptsFullyQualifiedBuildConfigInMetaData() {
29+
assertEquals(
30+
listOf("com.example.app.BuildConfig", "com.main.BuildConfig"),
31+
buildConfigClassCandidates("com.example.app", "com.main.BuildConfig"),
32+
)
33+
}
34+
35+
@Test
36+
fun buildConfigClassCandidates_doesNotDuplicateWhenMetaDataMatchesApplicationId() {
37+
assertEquals(
38+
listOf("com.example.app.BuildConfig"),
39+
buildConfigClassCandidates("com.example.app", "com.example.app"),
40+
)
41+
}
42+
43+
@Test
44+
fun buildConfigClassCandidates_ignoresBlankMetaData() {
45+
assertEquals(
46+
listOf("com.example.app.BuildConfig"),
47+
buildConfigClassCandidates("com.example.app", " "),
48+
)
49+
}
50+
}

docs/setup.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,18 @@ android {
208208
}
209209
```
210210

211+
### White-label apps (`applicationId``namespace`)
212+
213+
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`):
214+
215+
```xml
216+
<meta-data
217+
android:name="com.frontegg.reactnative.BUILD_CONFIG_PACKAGE"
218+
android:value="com.main" />
219+
```
220+
221+
Apps where `applicationId` matches `namespace` do not need this entry.
222+
211223
### Enable Android AssetLinks
212224

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

0 commit comments

Comments
 (0)