Skip to content

Commit 47c4640

Browse files
fix: Build warnings and compatibility issues across Android and MAUI (.NET) SDKs. (#439)
## Summary Fix build warnings and compatibility issues across Android and MAUI (.NET) SDKs, upgrade Dokka to 2.1.0, and make GeckoView lazy-loaded in the E2E app. ## Changes ### Android SDK (`observability-android`) - **Upgrade Dokka 2.0.0 → 2.1.0**: Migrated to the V2 plugin mode (`V2EnabledWithHelpers`) and adopted the new `dokka { }` DSL, replacing the deprecated `tasks.dokkaJavadoc` configuration. Added `dokka-javadoc` plugin. - **Replace deprecated `kotlinOptions` with `kotlin { compilerOptions { } }`**: Applies to both `observability-android` and `mobile-dotnet/android/native/LDObserve` Gradle build scripts. - **C native library adjustments**: Bumped compiler optimization from `-O2` to `-O3`; added `-Wl,-z,max-page-size=16384` linker option to support Android 16KB page-size devices. ### MAUI / .NET SDK (`mobile-dotnet`) - **Exclude LD Client SDK doc content files**: Added `ExcludeAssets="contentFiles"` to the `LaunchDarkly.ClientSdk` PackageReference in both `LDObservability.csproj` and `LDObservability.Fat.csproj` to prevent the LD SDK's content files from being included in the output. - **Fix deprecated Java boxing constructors**: Replaced `new Java.Lang.Boolean(b)`, `new Java.Lang.Integer(i)`, etc. with `Java.Lang.Boolean.ValueOf(b)`, `Java.Lang.Integer.ValueOf(i)`, etc. in `DictionaryTypeConverters.cs` to eliminate deprecation warnings. - **Fix nullable value type in dictionaries**: Changed `IDictionary<string, object>?` to `IDictionary<string, object?>?` in `ToJavaDictionary` to properly handle nullable attribute values. - **Remove unbindable Kotlin companion field**: Added a `remove-node` transform in `Metadata.xml` to suppress binding warning BG8400 for `LDObserveBridgeAdapter.Companion`. ### E2E Android App - **Lazy-load GeckoView**: GeckoView instances are no longer eagerly created — they are loaded on tap. This avoids crashes on emulators/CI where GeckoEngine is unavailable. Applies to both Compose (`LazyGeckoViewItem`) and XML (`setupLazyGeckoView`) activities. - **Layout restructure**: WebViews remain in a scrollable section; GeckoViews are placed outside the scroll area as fixed-height, tap-to-load containers. - **Add `android:extractNativeLibs="true"`** to `AndroidManifest.xml`. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk due to build/packaging and native build flag changes (Dokka upgrade, Kotlin compiler config, CMake linker options, and .NET binding transforms) that could affect release artifacts, plus UI behavior changes for GeckoView loading. > > **Overview** > Improves build compatibility across the Android and MAUI (.NET) SDKs by **upgrading Dokka to 2.1.0** (switching to the new `dokka {}` DSL / V2 plugin mode) and modernizing Kotlin Gradle config to `kotlin { compilerOptions { ... } }`. > > Adjusts packaging/binding behavior in the .NET Android binding and observability projects by suppressing additional binding warnings, removing an unbindable Kotlin `Companion` field via `Metadata.xml`, excluding `LaunchDarkly.ClientSdk` `contentFiles`, and tightening Android JNI marshalling helpers (nullable attribute values and `ValueOf` boxing). > > Updates the E2E Android app to **lazy-load GeckoView on tap** (Compose and XML), restructures the XML layout to keep WebViews scrollable while GeckoViews sit in fixed containers, adds `android:extractNativeLibs="true"`, and tweaks the native `tile_hash` build to `-O3` plus a 16KB page-size linker option. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ae9715c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 569a7b4 commit 47c4640

14 files changed

Lines changed: 241 additions & 168 deletions

File tree

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

Lines changed: 85 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import androidx.activity.ComponentActivity
88
import androidx.activity.compose.setContent
99
import androidx.activity.enableEdgeToEdge
1010
import androidx.compose.foundation.background
11+
import androidx.compose.foundation.clickable
12+
import androidx.compose.foundation.layout.Box
1113
import androidx.compose.foundation.layout.Column
1214
import androidx.compose.foundation.layout.fillMaxSize
1315
import androidx.compose.foundation.layout.fillMaxWidth
@@ -20,8 +22,10 @@ import androidx.compose.material3.Text
2022
import androidx.compose.runtime.Composable
2123
import androidx.compose.runtime.DisposableEffect
2224
import androidx.compose.runtime.LaunchedEffect
23-
import androidx.compose.runtime.key
25+
import androidx.compose.runtime.getValue
26+
import androidx.compose.runtime.mutableStateOf
2427
import androidx.compose.runtime.remember
28+
import androidx.compose.runtime.setValue
2529
import androidx.compose.ui.Alignment
2630
import androidx.compose.ui.Modifier
2731
import androidx.compose.ui.graphics.Color
@@ -46,75 +50,63 @@ class ComposeWebActivity : ComponentActivity() {
4650
val context = LocalContext.current
4751
val webView = remember(context) { WebView(context) }
4852
val customWebView = remember(context) { CustomWebView(context) }
49-
val geckoView = remember(context) { GeckoView(context) }
50-
val customGeckoView = remember(context) { CustomGeckoView(context) }
51-
5253
Column(
5354
modifier = Modifier
5455
.fillMaxSize()
5556
.padding(innerPadding)
56-
.verticalScroll(rememberScrollState())
5757
) {
58-
Text(
59-
text = "android.webkit.WebView",
60-
fontSize = 16.sp,
61-
modifier = Modifier
62-
.background(Color.Yellow)
63-
.align(Alignment.CenterHorizontally)
64-
)
65-
WebViewItem(
66-
url = "https://www.google.com",
67-
webView = webView,
58+
Column(
6859
modifier = Modifier
69-
.fillMaxWidth()
70-
.height(450.dp)
71-
)
60+
.weight(1f)
61+
.verticalScroll(rememberScrollState())
62+
) {
63+
Text(
64+
text = "android.webkit.WebView",
65+
fontSize = 16.sp,
66+
modifier = Modifier
67+
.background(Color.Yellow)
68+
.align(Alignment.CenterHorizontally)
69+
)
70+
WebViewItem(
71+
url = "https://www.google.com",
72+
webView = webView,
73+
modifier = Modifier
74+
.fillMaxWidth()
75+
.height(450.dp)
76+
)
7277

73-
Text(
74-
text = "CustomWebView",
75-
fontSize = 16.sp,
76-
modifier = Modifier
77-
.background(Color.Yellow)
78-
.align(Alignment.CenterHorizontally)
79-
)
80-
WebViewItem(
81-
url = "https://www.google.com",
82-
webView = customWebView,
83-
modifier = Modifier
84-
.fillMaxWidth()
85-
.height(450.dp)
86-
)
78+
Text(
79+
text = "CustomWebView",
80+
fontSize = 16.sp,
81+
modifier = Modifier
82+
.background(Color.Yellow)
83+
.align(Alignment.CenterHorizontally)
84+
)
85+
WebViewItem(
86+
url = "https://www.google.com",
87+
webView = customWebView,
88+
modifier = Modifier
89+
.fillMaxWidth()
90+
.height(450.dp)
91+
)
92+
}
8793

88-
Text(
89-
text = "org.mozilla.geckoview.GeckoView",
90-
fontSize = 16.sp,
91-
modifier = Modifier
92-
.align(Alignment.CenterHorizontally)
93-
.background(Color.Yellow)
94-
.padding(top = 8.dp)
95-
)
96-
GeckoViewItem(
94+
LazyGeckoViewItem(
95+
label = "org.mozilla.geckoview.GeckoView (device)",
9796
url = "https://www.google.com",
98-
geckoView = geckoView,
97+
geckoViewFactory = { GeckoView(it) },
9998
modifier = Modifier
10099
.fillMaxWidth()
101-
.height(450.dp)
100+
.height(200.dp)
102101
)
103102

104-
Text(
105-
text = "CustomGeckoView",
106-
fontSize = 16.sp,
107-
modifier = Modifier
108-
.align(Alignment.CenterHorizontally)
109-
.background(Color.Yellow)
110-
.padding(top = 8.dp)
111-
)
112-
GeckoViewItem(
103+
LazyGeckoViewItem(
104+
label = "CustomGeckoView (device)",
113105
url = "https://www.google.com",
114-
geckoView = customGeckoView,
106+
geckoViewFactory = { CustomGeckoView(it) },
115107
modifier = Modifier
116108
.fillMaxWidth()
117-
.height(450.dp)
109+
.height(200.dp)
118110
)
119111
}
120112
}
@@ -152,36 +144,52 @@ fun WebViewItem(url: String, webView: WebView, modifier: Modifier = Modifier) {
152144
}
153145

154146
@Composable
155-
fun GeckoViewItem(url: String, geckoView: GeckoView, modifier: Modifier = Modifier) {
156-
val context = LocalContext.current
157-
val runtime = remember {
158-
GeckoRuntime.getDefault(context.applicationContext)
159-
}
160-
val session = remember(runtime) {
161-
GeckoSession().apply {
162-
setContentDelegate(object : ContentDelegate {})
163-
open(runtime)
147+
fun LazyGeckoViewItem(
148+
label: String,
149+
url: String,
150+
geckoViewFactory: (android.content.Context) -> GeckoView,
151+
modifier: Modifier = Modifier
152+
) {
153+
var loaded by remember { mutableStateOf(false) }
154+
155+
Text(
156+
text = if (loaded) label else "Tap to load $label",
157+
fontSize = 16.sp,
158+
modifier = Modifier
159+
.background(Color.Yellow)
160+
.padding(top = 8.dp)
161+
.then(if (!loaded) Modifier.clickable { loaded = true } else Modifier)
162+
)
163+
164+
if (loaded) {
165+
val context = LocalContext.current
166+
val geckoView = remember(context) { geckoViewFactory(context) }
167+
val runtime = remember { GeckoRuntime.getDefault(context.applicationContext) }
168+
val session = remember(runtime) {
169+
GeckoSession().apply {
170+
setContentDelegate(object : ContentDelegate {})
171+
open(runtime)
172+
}
164173
}
165-
}
166174

167-
DisposableEffect(session) {
168-
onDispose {
169-
session.close()
175+
DisposableEffect(session) {
176+
onDispose { session.close() }
170177
}
171-
}
172178

173-
key(geckoView) {
174179
AndroidView(
175180
modifier = modifier,
176-
factory = { _ ->
177-
geckoView.apply {
178-
setSession(session)
179-
}
180-
}
181+
factory = { _ -> geckoView.apply { setSession(session) } }
181182
)
182-
}
183183

184-
LaunchedEffect(url) {
185-
session.loadUri(url)
184+
LaunchedEffect(url) { session.loadUri(url) }
185+
} else {
186+
Box(
187+
modifier = modifier
188+
.background(Color.LightGray)
189+
.clickable { loaded = true },
190+
contentAlignment = Alignment.Center
191+
) {
192+
Text("Tap to load", color = Color.DarkGray)
193+
}
186194
}
187195
}

e2e/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
android:supportsRtl="true"
1616
android:theme="@style/Theme.AndroidObservability"
1717
android:usesCleartextTraffic="true"
18+
android:extractNativeLibs="true"
1819
tools:targetApi="31" >
1920
<activity
2021
android:name=".masking.XMLUserFormActivity"

e2e/android/app/src/main/java/com/example/androidobservability/masking/XMLWebActivity.kt

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import android.annotation.SuppressLint
44
import android.os.Bundle
55
import android.webkit.WebView
66
import android.webkit.WebViewClient
7+
import android.widget.FrameLayout
8+
import android.widget.TextView
79
import androidx.activity.ComponentActivity
810
import androidx.annotation.IdRes
911
import com.example.androidobservability.R
@@ -22,8 +24,19 @@ class XMLWebActivity : ComponentActivity() {
2224

2325
setupWebView(R.id.webview)
2426
setupWebView(R.id.customWebView)
25-
setupGeckoWebView(R.id.geckoview)
26-
setupGeckoWebView(R.id.customGeckoView)
27+
28+
setupLazyGeckoView(
29+
labelId = R.id.geckoLabel,
30+
containerId = R.id.geckoContainer,
31+
factory = { GeckoView(this) },
32+
label = "org.mozilla.geckoview.GeckoView (device)"
33+
)
34+
setupLazyGeckoView(
35+
labelId = R.id.customGeckoLabel,
36+
containerId = R.id.customGeckoContainer,
37+
factory = { CustomGeckoView(this) },
38+
label = "CustomGeckoView (device)"
39+
)
2740
}
2841

2942
@SuppressLint("SetJavaScriptEnabled")
@@ -34,16 +47,41 @@ class XMLWebActivity : ComponentActivity() {
3447
webView.loadUrl(url)
3548
}
3649

37-
private fun setupGeckoWebView(@IdRes geckoViewId: Int) {
38-
val view = findViewById<GeckoView?>(geckoViewId)
39-
val session = GeckoSession()
50+
private fun setupLazyGeckoView(
51+
@IdRes labelId: Int,
52+
@IdRes containerId: Int,
53+
factory: () -> GeckoView,
54+
label: String
55+
) {
56+
val labelView = findViewById<TextView>(labelId)
57+
val container = findViewById<FrameLayout>(containerId)
4058

41-
session.setContentDelegate(object : ContentDelegate {})
59+
labelView.setOnClickListener { loadGeckoView(container, factory(), label, labelView) }
60+
container.setOnClickListener { loadGeckoView(container, factory(), label, labelView) }
61+
}
4262

43-
GeckoRuntime.getDefault(application).let {
44-
session.open(it)
45-
}
46-
view?.setSession(session)
63+
private fun loadGeckoView(
64+
container: FrameLayout,
65+
geckoView: GeckoView,
66+
label: String,
67+
labelView: TextView
68+
) {
69+
container.setOnClickListener(null)
70+
labelView.setOnClickListener(null)
71+
labelView.text = label
72+
73+
val session = GeckoSession()
74+
session.setContentDelegate(object : ContentDelegate {})
75+
GeckoRuntime.getDefault(application).let { session.open(it) }
76+
geckoView.setSession(session)
4777
session.loadUri(url)
78+
79+
container.addView(
80+
geckoView,
81+
FrameLayout.LayoutParams(
82+
FrameLayout.LayoutParams.MATCH_PARENT,
83+
FrameLayout.LayoutParams.MATCH_PARENT
84+
)
85+
)
4886
}
4987
}

0 commit comments

Comments
 (0)