Skip to content

Commit 8f3a671

Browse files
feat: Optional Jet Compose (#402)
## Summary - Configure project so if customers didn't include Jet Compose library then it wasn't activated - Create flavor only XML Views test app https://launchdarkly.atlassian.net/browse/O11Y-812 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it changes session-replay masking traversal and Gradle dependency scoping to support apps without Compose; mistakes here could break masking collection or cause runtime `ClassNotFoundException`s in non-Compose apps. > > **Overview** > Makes Jetpack Compose support *optional* in the Android observability SDK by switching Compose UI artifacts to `compileOnly`, splitting the masking API so Compose-specific extensions live in a new `ComposeMaskingAPI.kt`, and guarding Compose traversal in `MaskCollector` behind a runtime classpath check. > > Updates the Android E2E app to test both paths by adding `compose`/`noCompose` product flavors, moving Compose activities/launcher to a flavor manifest, and introducing a full XML-based `MainActivity` + layout for the `noCompose` variant; CI is adjusted to run the Compose-flavor unit tests. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b7d268c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 5ff93f6 commit 8f3a671

22 files changed

Lines changed: 639 additions & 75 deletions

File tree

.github/workflows/android-e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2 commit SHA - https://github.com/android-actions/setup-android/releases/tag/v3.2.2
4141

4242
- name: Run unit tests
43-
run: ./gradlew :app:testDebugUnitTest
43+
run: ./gradlew :app:testComposeDebugUnitTest
4444

4545
- name: Upload test reports
4646
if: always()

e2e/android/app/build.gradle.kts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ android {
5252
)
5353
}
5454
}
55+
flavorDimensions += "uiFramework"
56+
productFlavors {
57+
create("compose") {
58+
dimension = "uiFramework"
59+
}
60+
create("noCompose") {
61+
dimension = "uiFramework"
62+
applicationIdSuffix = ".nocompose"
63+
}
64+
}
5565
compileOptions {
5666
sourceCompatibility = JavaVersion.VERSION_11
5767
targetCompatibility = JavaVersion.VERSION_11
@@ -103,29 +113,34 @@ dependencies {
103113
implementation("androidx.recyclerview:recyclerview:1.3.2")
104114
implementation(libs.androidx.core.ktx)
105115
implementation(libs.androidx.lifecycle.runtime.ktx)
106-
implementation(libs.androidx.activity.compose)
116+
117+
// Compose runtime is needed by the Kotlin Compose compiler plugin (applied project-wide).
118+
// It does NOT contain any UI classes like AbstractComposeView, so the SDK's
119+
// isComposeAvailable runtime check still returns false in the noCompose variant.
107120
implementation(platform(libs.androidx.compose.bom))
108-
implementation(libs.androidx.ui)
109-
implementation(libs.androidx.ui.graphics)
110-
implementation(libs.androidx.ui.tooling.preview)
111-
implementation(libs.androidx.material3)
121+
implementation("androidx.compose.runtime:runtime")
122+
123+
// Compose UI dependencies -- only for the compose flavor
124+
"composeImplementation"(libs.androidx.activity.compose)
125+
"composeImplementation"(libs.androidx.ui)
126+
"composeImplementation"(libs.androidx.ui.graphics)
127+
"composeImplementation"(libs.androidx.ui.tooling.preview)
128+
"composeImplementation"(libs.androidx.material3)
129+
130+
// noCompose uses AppCompatActivity for proper Material Components theme resolution
131+
"noComposeImplementation"("androidx.appcompat:appcompat:1.7.0")
112132

113133
testImplementation(libs.junit)
114-
testImplementation(libs.androidx.ui.test.junit4)
115134
testImplementation(libs.core.ktx)
116135
testImplementation(libs.robolectric)
117136
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
118137
testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.51.0")
138+
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
119139
testImplementation(testFixtures(project(":observability-android")))
120140

121141
// Used for testing webviews masking
122142
implementation("org.mozilla.geckoview:geckoview:130.0.20240913135723")
123143

124144
androidTestImplementation(libs.androidx.junit)
125145
androidTestImplementation(libs.androidx.espresso.core)
126-
androidTestImplementation(platform(libs.androidx.compose.bom))
127-
androidTestImplementation(libs.androidx.ui.test.junit4)
128-
129-
debugImplementation(libs.androidx.ui.tooling)
130-
debugImplementation(libs.androidx.ui.test.manifest)
131146
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application>
5+
<activity
6+
android:name=".masking.ComposeUserFormActivity"
7+
android:exported="false"
8+
android:label="@string/title_activity_secondary"
9+
android:theme="@style/Theme.AndroidObservability" />
10+
<activity
11+
android:name=".masking.ComposeMaskingActivity"
12+
android:exported="false"
13+
android:label="Compose Masking"
14+
android:theme="@style/Theme.AndroidObservability" />
15+
<activity
16+
android:name=".masking.ComposeWebActivity"
17+
android:exported="false" />
18+
19+
<activity
20+
android:name=".MainActivity"
21+
android:exported="true"
22+
android:theme="@style/Theme.AndroidObservability" >
23+
<intent-filter>
24+
<action android:name="android.intent.action.MAIN" />
25+
<category android:name="android.intent.category.LAUNCHER" />
26+
</intent-filter>
27+
</activity>
28+
</application>
29+
30+
</manifest>

e2e/android/app/src/main/java/com/example/androidobservability/MainActivity.kt renamed to e2e/android/app/src/compose/java/com/example/androidobservability/MainActivity.kt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,14 @@ import androidx.compose.ui.unit.dp
3939
import androidx.compose.foundation.layout.PaddingValues
4040
import androidx.compose.foundation.layout.FlowRow
4141
import androidx.compose.foundation.layout.ExperimentalLayoutApi
42+
import androidx.compose.material3.ExperimentalMaterial3Api
4243
import androidx.compose.material3.HorizontalDivider
44+
import androidx.compose.material3.Icon
45+
import androidx.compose.material3.TopAppBar
46+
import androidx.compose.material3.TopAppBarDefaults
4347
import androidx.compose.ui.platform.LocalContext
4448
import androidx.compose.ui.graphics.Color
49+
import androidx.compose.ui.res.painterResource
4550
import com.example.androidobservability.masking.ComposeMaskingActivity
4651
import com.example.androidobservability.masking.ComposeUserFormActivity
4752
import com.example.androidobservability.masking.ComposeWebActivity
@@ -65,10 +70,33 @@ class MainActivity : ComponentActivity() {
6570
enableEdgeToEdge()
6671
setContent {
6772
AndroidObservabilityTheme {
73+
@OptIn(ExperimentalMaterial3Api::class)
6874
Scaffold(
6975
modifier = Modifier
7076
.fillMaxSize()
71-
.imePadding()
77+
.imePadding(),
78+
topBar = {
79+
TopAppBar(
80+
title = {
81+
Text(
82+
"Android Observability",
83+
modifier = Modifier.padding(start = 8.dp)
84+
)
85+
},
86+
navigationIcon = {
87+
Icon(
88+
painter = painterResource(id = R.drawable.ic_launchdarkly_logo),
89+
contentDescription = "LaunchDarkly",
90+
modifier = Modifier.padding(start = 12.dp),
91+
tint = Color.White
92+
)
93+
},
94+
colors = TopAppBarDefaults.topAppBarColors(
95+
containerColor = Color.Black,
96+
titleContentColor = Color.White
97+
)
98+
)
99+
}
72100
) { innerPadding ->
73101
MainScreen(viewModel, innerPadding)
74102
}

e2e/android/app/src/main/java/com/example/androidobservability/masking/ComposeMaskingActivity.kt renamed to e2e/android/app/src/compose/java/com/example/androidobservability/masking/ComposeMaskingActivity.kt

File renamed without changes.

e2e/android/app/src/main/java/com/example/androidobservability/masking/ComposeUserForm.kt renamed to e2e/android/app/src/compose/java/com/example/androidobservability/masking/ComposeUserForm.kt

File renamed without changes.

e2e/android/app/src/main/java/com/example/androidobservability/masking/ComposeWebActivity.kt renamed to e2e/android/app/src/compose/java/com/example/androidobservability/masking/ComposeWebActivity.kt

File renamed without changes.

e2e/android/app/src/main/java/com/example/androidobservability/ui/theme/Color.kt renamed to e2e/android/app/src/compose/java/com/example/androidobservability/ui/theme/Color.kt

File renamed without changes.

e2e/android/app/src/main/java/com/example/androidobservability/ui/theme/Theme.kt renamed to e2e/android/app/src/compose/java/com/example/androidobservability/ui/theme/Theme.kt

File renamed without changes.

e2e/android/app/src/main/java/com/example/androidobservability/ui/theme/Type.kt renamed to e2e/android/app/src/compose/java/com/example/androidobservability/ui/theme/Type.kt

File renamed without changes.

0 commit comments

Comments
 (0)