Skip to content

fix(maui): .NET 10 Android Release build fixes (relocate okio/okhttp) - #791

Merged
abelonogov-ld merged 7 commits into
mainfrom
feat/maui-otel-upgrade-net10
Oct 6, 2026
Merged

abelonogov-ld merged 7 commits into
mainfrom
feat/maui-otel-upgrade-net10

Conversation

@abelonogov-ld

@abelonogov-ld abelonogov-ld commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A customer (Delta Faucet) cannot build a MAUI app for net10.0-android in Release with LaunchDarkly.SessionReplay. R8 rejects two classes as duplicates:

  • okio/-DeflaterSinkExtensions
  • kotlinx.serialization.json.Json$Default

Both come from ldobserve-otel-bundle.jar, the fat JAR we ship inside the package. The app acquires its own copy of the same classes through unrelated bindings — Square.OkIO arrives transitively via AndroidX.DataStore, which Firebase depends on — and two definitions of a class is a hard failure for R8. It only surfaces in Release, because Debug does not run R8.

This is not new to any particular OTel version. The released 0.10.2 bundle already contains 433 unshaded okio/ and okhttp3/ entries, including the exact class R8 names.

Fix

okio/okhttp are relocated under com/launchdarkly/sessionreplay/shaded/ by BundleOtelJarsTask, using an ASM ClassRemapper.

They cannot simply be dropped: the OTLP exporters resolve their transport through ServiceLoader, the only HttpSenderProvider we register is the OkHttp one, and OkHttp's entire I/O layer is Okio. Removing either produces the "No HttpSenderProvider found on classpath" crash at runtime. Relocating makes the collision impossible instead of depending on the app's dependency graph staying clear.

The remapper also rewrites string constants, not just bytecode class references. OkHttp builds the path to its public-suffix resource by concatenating the literal "okhttp3/internal/publicsuffix/" with a simple class name, so a remapper that only moved class references would relocate the resource and leave the lookup pointing at the old path.

kotlinx.serialization is already gone from observability-android as of #787, which replaced it with org.json. This PR drops the now-unused Org.Jetbrains.Kotlinx.KotlinxSerializationJson package references from the binding and the fat package. That reference was the other half of the duplicate-class failure: its targets file hardcoded a path into $(Home)/.gradle/caches/....

Also in this PR

  • MauiSample10.csproj — a .NET 10 variant of the sample, sharing sources with MauiSample9.csproj. global.json is removed so both SDK bands resolve. Not added to the .sln.
  • Firebase is a permanent dependency of the .NET 10 sample. It is the shortest path to reproducing this class of bug (Firebase.Common → AndroidX.DataStore.Preferences → Square.OkIO.JVM), so it stays as a standing compatibility check rather than a one-off repro.
  • NoWarn for NU1605/NU1608 removed from the sample, so dependency regressions are visible instead of silent.
  • Analytics(pageViews = false) → Analytics(screenViews = false) in the MAUI Kotlin bridge. main's ObservabilityOptions.Analytics has had screenViews for a while; the MAUI bridge was still passing pageViews and would not compile against it. The MAUI native Gradle build is not in CI, which is why this went unnoticed.
  • Lifecycle floors stay at 2.9.2.1, with a comment explaining why. Bumping them to 2.11.0.1 forces SavedState 1.5 / Collection 1.6 on consumers and turns our own SavedState and Collection floors into NU1605 downgrade errors.
  • Navigation bar title in the sample now shows the configuration and target framework (Debug/Release, .NET 9/10).
  • README brought back in line with the shipped API: corrected the automatic-crash-reporting claim, updated prerequisites, documented explicit ObservabilityPlugin registration, and named both sample projects.

Split out: the OTel upgrade

This branch originally also upgraded to OTel Java 1.66 and OTel Android 1.7. That has been moved to feat/otel-1.7-upgrade.

OTel Android 1.7 pulls androidx.core 1.19, androidx.lifecycle 2.11 and okhttp-android 5.4, all of which declare a minimum compileSdk of 36–37 and a minimum AGP of 9.1. Our e2e app (compileSdk 35, AGP 8.13.2) fails checkComposeDebugAarMetadata on them, and so would every native Android consumer still on AGP 8. The resolutionStrategy pins that make it build locally only apply inside our own Gradle build; they are not published, so consumers see the raw transitives.

Since the relocation fix is independent of the OTel version, there is no reason to hold the customer fix behind an AGP 9 migration.

Verification

  • Bundle JAR at OTel 1.51: 0 unshaded okio//okhttp3/ entries, 433 relocated under the shaded prefix. -DeflaterSinkExtensions is relocated; the publicsuffixes.gz resource moves with its database class.
  • META-INF/services intact after the merge; HttpSenderProvider still resolves to OkHttpHttpSenderProvider.
  • Clean rebuild of the native module and the Android binding in Release resolves OTel back to 0.11.0-alpha, androidx.core 1.16, lifecycle 2.8.7.
  • MauiSample10 builds for net10.0-android in Release with Firebase present: R8 passes, signed APK and AAB produced, 0 errors. This is the customer's failing configuration.
  • No NU1605 in the sample restore. Nine NU1608 warnings remain, all from MAUI 10 / Firebase pulling AndroidX base packages ahead of their .Ktx siblings; they are not caused by this package and are unchanged from main's pins.

Not verified

OTLP export through the relocated OkHttp has not been exercised on a device. The SPI registration and the public-suffix resource are checked statically only.


Note

Overview
Fixes .NET 10 Android Release builds that fail R8 duplicate-class checks when LaunchDarkly.SessionReplay is used alongside common deps (e.g. Firebase → DataStore → OkIO).

Android OTel bundle: BundleOtelJarsTask now relocates okio and okhttp3 under com.launchdarkly.sessionreplay.shaded/ via ASM (classes, resources, and string constants such as OkHttp’s public-suffix paths). OkHttp/Okio stay in the bundle for OTLP HttpSenderProvider, but no longer collide with app copies. Org.Jetbrains.Kotlinx.KotlinxSerializationJson is removed from the Android binding and fat package to avoid the second duplicate (kotlinx.serialization.json.Json$Default).

MAUI bridge: ObservabilityOptions.Analytics uses screenViews = false (was obsolete pageViews) so Android navigation isn’t double-counted with MAUI screen reporting.

Samples & docs: Adds MauiSample10.csproj (shared sources with .NET 9), Firebase on the .NET 10 Android sample as a standing Release R8 check, HttpActivityPropagationSupport on both samples, and README updates (.NET 9/10, HTTP tracing, plugin registration, crash reporting). Package version 0.11.3; global.json removed for multi-SDK builds.

Reviewed by Cursor Bugbot for commit 43a4eeb. Bugbot is set up for automated code reviews on this repo. Configure here.

abelonogov-ld and others added 2 commits October 2, 2026 17:00
…ndled okio/okhttp

- Bump OpenTelemetry Java 1.51.0 -> 1.66.0 and OpenTelemetry Android 0.11.0-alpha -> 1.7.0
  in observability-android and the MAUI LDObserve native module; migrate to the split
  SessionProvider/SessionPublisher API, RumBuilder, and the new AttributeType.VALUE /
  ValueType.EMPTY cases.
- Pin kotlinx-coroutines, kotlin-stdlib, androidx.core and okhttp to versions the current
  Kotlin 2.0.21 / AGP 8.x toolchain can build against.
- Drop the Org.Jetbrains.Kotlinx.KotlinxSerializationJson NuGet dependency. Its JVM package
  injects a jar from a hardcoded Gradle cache path, which duplicated
  kotlinx.serialization.json classes in apps using Xamarin.KotlinX.Serialization.Json.
- Relocate the okio and okhttp3 classes bundled in ldobserve-otel-bundle.jar under
  com.launchdarkly.sessionreplay.shaded so they no longer collide with Square.OkIO
  (R8 'defined multiple times' failure in Release builds, e.g. via Firebase).
- Update NativeAndroidDeps.props globs for the renamed OTel Android 1.x artifacts.
- Fix ObservabilityBridge for the pageViews -> screenViews rename.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add MauiSample10.csproj (net10.0-android/ios) sharing sources with MauiSample9, with
  isolated obj/bin paths.
- Reference Xamarin.Firebase.Messaging/Analytics on Android as a standing check against
  the Square.OkIO / KotlinX duplicate-class conflicts (exercised by Release builds).
- Guard CommunityToolkit.Maui Popup API differences (v11 vs v15) in DialogsPage.
- Show platform, .NET runtime version and Debug/Release in the shell title.
- Remove global.json so the .NET 10 SDK can build the tree.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abelonogov-ld
abelonogov-ld requested a review from a team as a code owner October 3, 2026 00:02
@abelonogov-ld
abelonogov-ld marked this pull request as draft October 3, 2026 00:02
… consumer apps

0.11.1 raised Xamarin.AndroidX.Lifecycle.* floors to 2.11.0.1, which requires
SavedState >= 1.5.0.1 and Collection.Jvm >= 1.6.0.1 and conflicts with our
SavedState 1.3.1.1 / Collection 1.5.0.3 floors, producing NU1605 downgrade
errors in MAUI apps (e.g. MauiPlanets on MAUI 9). Our Java only uses lifecycle
APIs present in 2.9 (ProcessLifecycleOwner, DefaultLifecycleObserver), so go
back to the 2.9.2.1 line MAUI itself ships.

Stop suppressing NU1605/NU1608 in MauiSample10 so the sample catches this, and
bump the package version to 0.11.2.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abelonogov-ld abelonogov-ld changed the title Draft Feat/maui otel upgrade net10 fix(maui): .NET 10 Android Release build fixes (shade okio/okhttp, OTel Android 1.7) Oct 6, 2026
@abelonogov-ld
abelonogov-ld marked this pull request as ready for review October 6, 2026 02:19
@abelonogov-ld abelonogov-ld changed the title fix(maui): .NET 10 Android Release build fixes (shade okio/okhttp, OTel Android 1.7) fix: O11Y-2006 .NET 10 Android Release build fixes (shade okio/okhttp, OTel Android 1.7) Oct 6, 2026
abelonogov-ld and others added 2 commits October 5, 2026 19:31
Co-authored-by: Cursor <cursoragent@cursor.com>
The okio/okhttp relocation is what fixes the R8 duplicate-class failure in
Release, and it is independent of the OTel version: 0.10.2, which shipped on
OTel 1.51, already bundles 433 unshaded okio/okhttp3 entries including the
okio/-DeflaterSinkExtensions that R8 rejects.

Upgrading to OTel Android 1.7 pulls androidx.core 1.19, androidx.lifecycle 2.11
and okhttp-android 5.4, which require compileSdk 36-37 and AGP 9.1. That breaks
every native Android consumer still on AGP 8, including our own e2e app, and is
a migration the Android owners should land on its own schedule.

Reverts the version bumps and the observability-android changes that existed
only to track the 1.x API. Keeps the relocation, the Lifecycle floors, the
.NET 10 sample and the Analytics.screenViews fix.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abelonogov-ld abelonogov-ld changed the title fix: O11Y-2006 .NET 10 Android Release build fixes (shade okio/okhttp, OTel Android 1.7) fix(maui): .NET 10 Android Release build fixes (relocate okio/okhttp) Oct 6, 2026

@Vadman97 Vadman97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the fix!

@abelonogov-ld
abelonogov-ld merged commit 20ab1b7 into main Oct 6, 2026
23 checks passed
@abelonogov-ld
abelonogov-ld deleted the feat/maui-otel-upgrade-net10 branch October 6, 2026 19:24
abelonogov-ld added a commit that referenced this pull request Oct 6, 2026
* main:
  fix(maui): .NET 10 Android Release build fixes (relocate okio/okhttp) (#791)
abelonogov-ld added a commit that referenced this pull request Oct 6, 2026
Stacked on #791. Review that first; this PR's diff is only the API work
on top of it.

## Summary

Brings the MAUI public API closer to the Flutter observability SDK.
Every option below already existed in the native Android and iOS SDKs.
MAUI simply didn't expose it, so this is mostly plumbing through the
Kotlin and Swift bridges and the binding definitions.

### Observability options

- **`CustomHeaders`** — extra headers sent with OTLP export.
- **`LogsApiLevel`** — minimum severity for logs sent through
`LDObserve.RecordLog`. Uses a new `ObservabilityLogLevel` enum that
follows the OpenTelemetry severity numbers (`Trace` = 1 … `Fatal4` =
24), plus `None` to turn logging off. The default is `Info`.
- **`ServiceVersion`** now defaults to `null` and resolves to the app's
version (`AppInfo.Current.VersionString`) instead of a hardcoded
constant. `DefaultServiceVersion` is marked `[Obsolete]`. **Behavior
change:** apps that don't set it now report their real version.
- **`LDObserve.NativeVersion`** — the version of the underlying native
SDK, useful in support requests.

### Session replay options

- **Capture:** `SampleRate`, `FrameRate`, `Scale`, `ImageQuality`
(defaults 1.0 / 1.0 / 1.0 / 0.3, the same as native).
- **Privacy:** `MinimumAlpha` is now passed through on iOS too.
- **`LDIgnore()`** view extension, next to the existing `LDMask()` /
`LDUnmask()`.

On Android, "ignore" maps to mask, because the native Android SDK has no
per-view ignore. The README says so.

### Docs

The README now covers the new options, capture settings and
`NativeVersion`.

## Not in this PR

- Type-based privacy rules (`MaskViewTypes`, `UnmaskViewTypes`,
`IgnoreViewTypes`) moved to the `feat/maui-privacy-view-types` branch,
stacked on this one, so they can be reviewed separately.
- `LDReplay.TrackEvaluation` is effectively dead: nothing calls it,
there's no native entry point, and evaluations are already recorded as
`evaluation` spans by the observability hook. I suggest marking it
`[Obsolete]` separately.
- Remaining Flutter-parity gaps: init readiness result, `Shutdown()`,
`Track` / `TrackScreenView` / `TrackClick`, `WithSpan`, log capture, and
the `analytics` / `traces` / `metricsEnabled` toggles.
- The Android bridge passes `Analytics(screenViews = false)` because
MAUI is meant to report screen views itself, but MAUI never does. So
Android currently records no screen views. Worth a follow-up.

## Verification

- Built the sample on Android and iOS, and checked the new fields in the
generated Android binding and in the rebuilt xcframework.
- After #791 dropped the OTel upgrade, rebuilt the Android binding
cleanly against the reverted `observability-android`. `CustomHeaders`,
`LogsApiLevel` and `SampleRate` are all present in the generated C#.
- **Not verified on a device:** `CustomHeaders` arriving on export.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Expands the MAUI observability SDK so more native settings are
configurable from .NET and documented, aligning with the Flutter SDK.
> 
> **Observability:** Adds `CustomHeaders` and `LogsApiLevel` (new
`ObservabilityLogLevel` enum, OTel severities plus `None`).
`ServiceVersion` is optional and, when unset, resolves to
`AppInfo.Current.VersionString` instead of a fixed default
(`DefaultServiceVersion` is obsolete). Exposes `LDObserve.NativeVersion`
after init. Values are passed through the Android and iOS bridges.
> 
> **Session replay:** Adds capture knobs `SampleRate`, `FrameRate`,
`Scale`, and `ImageQuality`, and wires `Privacy.MinimumAlpha` through on
iOS. Adds `View.LDIgnore()` (Android falls back to mask). README covers
observability options, capture settings, masking timing, and
`NativeVersion`.
> 
> Package version bumps to **0.14.1**.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
bada0a0. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants