diff --git a/e2e/android/app/build.gradle.kts b/e2e/android/app/build.gradle.kts
index a60539c958..65b96bdb1c 100644
--- a/e2e/android/app/build.gradle.kts
+++ b/e2e/android/app/build.gradle.kts
@@ -104,22 +104,22 @@ dependencies {
implementation("com.launchdarkly:launchdarkly-android-client-sdk:5.11.0")
- implementation("io.opentelemetry:opentelemetry-api:1.51.0")
- implementation("io.opentelemetry:opentelemetry-sdk:1.51.0")
- implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.51.0")
- implementation("io.opentelemetry:opentelemetry-sdk-metrics:1.51.0")
+ implementation("io.opentelemetry:opentelemetry-api:1.52.0")
+ implementation("io.opentelemetry:opentelemetry-sdk:1.52.0")
+ implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.52.0")
+ implementation("io.opentelemetry:opentelemetry-sdk-metrics:1.52.0")
// Android HTTP Url instrumentation
- implementation("io.opentelemetry.android.instrumentation:httpurlconnection-library:0.11.0-alpha")
- byteBuddy("io.opentelemetry.android.instrumentation:httpurlconnection-agent:0.11.0-alpha")
+ implementation("io.opentelemetry.android.instrumentation:httpurlconnection-library:0.13.0-alpha")
+ byteBuddy("io.opentelemetry.android.instrumentation:httpurlconnection-agent:0.13.0-alpha")
// Used for accessing the SignalFromDiskExporter class in TestApplication
- implementation("io.opentelemetry.android:core:0.11.0-alpha")
+ implementation("io.opentelemetry.android:core:0.13.0-alpha")
// OkHTTP instrumentation
- implementation("io.opentelemetry.android.instrumentation:okhttp3-library:0.11.0-alpha")
- byteBuddy("io.opentelemetry.android.instrumentation:okhttp3-agent:0.11.0-alpha")
- implementation("com.squareup.okhttp3:okhttp:4.12.0")
+ implementation("io.opentelemetry.android.instrumentation:okhttp3-library:0.13.0-alpha")
+ byteBuddy("io.opentelemetry.android.instrumentation:okhttp3-agent:0.13.0-alpha")
+ implementation("com.squareup.okhttp3:okhttp:5.1.0")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.recyclerview:recyclerview:1.3.2")
@@ -147,8 +147,8 @@ dependencies {
testImplementation(libs.junit)
testImplementation(libs.core.ktx)
testImplementation(libs.robolectric)
- testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
- testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.51.0")
+ testImplementation("com.squareup.okhttp3:mockwebserver:5.1.0")
+ testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.52.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
testImplementation(testFixtures(project(":observability-android")))
diff --git a/e2e/android/app/src/testCompose/java/com/example/androidobservability/DisablingConfigOptionsE2ETest.kt b/e2e/android/app/src/testCompose/java/com/example/androidobservability/DisablingConfigOptionsE2ETest.kt
index 9efaa1251c..0de39e4c4c 100644
--- a/e2e/android/app/src/testCompose/java/com/example/androidobservability/DisablingConfigOptionsE2ETest.kt
+++ b/e2e/android/app/src/testCompose/java/com/example/androidobservability/DisablingConfigOptionsE2ETest.kt
@@ -14,6 +14,7 @@ import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
+import okhttp3.mockwebserver.RecordedRequest
import org.junit.After
import org.junit.Ignore
import org.junit.Test
@@ -264,17 +265,21 @@ class DisablingConfigOptionsE2ETest {
private fun requestsContainsUrl(url: String): Boolean {
while (true) {
val request = application.mockWebServer?.takeRequest(100, TimeUnit.MILLISECONDS) ?: return false
- if (request.requestUrl.toString() == url) return true
+ if (recordedUrl(request) == url) return true
}
}
private fun requestsStartWith(prefix: String): Boolean {
while (true) {
val request = application.mockWebServer?.takeRequest(100, TimeUnit.MILLISECONDS) ?: return false
- if (request.requestUrl.toString().startsWith(prefix)) return true
+ if (recordedUrl(request).startsWith(prefix)) return true
}
}
+ // MockWebServer 5 records the host as 127.0.0.1, while the expected URLs use `localhost`.
+ private fun recordedUrl(request: RecordedRequest): String =
+ request.requestUrl?.newBuilder()?.host("localhost")?.build().toString()
+
private fun triggerTestLog(severity: Severity = Severity.INFO) {
LDObserve.recordLog(
message = TEST_LOG_MESSAGE,
diff --git a/sdk/@launchdarkly/mobile-dotnet/.vscode/settings.json b/sdk/@launchdarkly/mobile-dotnet/.vscode/settings.json
new file mode 100644
index 0000000000..a70fbfc50c
--- /dev/null
+++ b/sdk/@launchdarkly/mobile-dotnet/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dotnet.defaultSolution": "mobile-dotnet.sln"
+}
\ No newline at end of file
diff --git a/sdk/@launchdarkly/mobile-dotnet/README.md b/sdk/@launchdarkly/mobile-dotnet/README.md
index d24be4e430..39997fe6a8 100644
--- a/sdk/@launchdarkly/mobile-dotnet/README.md
+++ b/sdk/@launchdarkly/mobile-dotnet/README.md
@@ -11,19 +11,33 @@ The LaunchDarkly Observability SDK for .NET MAUI provides automatic and manual i
### Automatic Instrumentation
The .NET MAUI observability plugin automatically instruments:
-- **HTTP Requests**: Outgoing HTTP requests
-- **Crash Reporting**: Automatic crash reporting and stack traces
+- **HTTP Requests**: Outgoing HTTP requests made through `HttpClient` (`instrumentation.networkRequests`; requires `HttpActivityPropagationSupport`, see [Enabling HTTP request tracing](#enabling-http-request-tracing))
+- **Launch Times**: App launch performance (`instrumentation.launchTimes`)
- **Feature Flag Evaluations**: Evaluation events added to your spans
- **Session Management**: User session tracking and background timeout handling
+Automatic crash reporting is not enabled yet; use `LDObserve.RecordError` to report exceptions.
+
## Prerequisites
-* **.NET 9.0** or higher is required.
-* MAUI support for **iOS** and **Android**.
+* **.NET 9** or **.NET 10** with the MAUI workload.
+* **Android** API 23 or later and **iOS** 15 or later.
+
+### Enabling HTTP request tracing
+
+HTTP tracing needs one setting in your app's project file:
+
+```xml
+
+ true
+
+```
+
+The Android SDK sets this to `false` by default in every configuration, and the iOS SDK sets it to `false` in Release builds. While it is `false`, the trimmer removes the `System.Net.Http` diagnostics that the SDK listens to, so no HTTP spans are recorded even with `networkRequests: true`. The SDK cannot turn it back on at runtime.
## Example Application
-A complete example application is available in the [sample](./sample) directory.
+A complete example application is available in the [sample](./sample) directory. `MauiSample9.csproj` builds it for .NET 9 and `MauiSample10.csproj` for .NET 10; both share the same sources.
## Usage
@@ -64,6 +78,21 @@ public static class MauiProgram
}
```
+Alternatively, register the plugin yourself when building the client configuration. `ObservabilityPlugin` takes the same options, including optional `SessionReplayOptions` as its second argument:
+
+```csharp
+using LaunchDarkly.Sdk.Client.Integrations;
+using LaunchDarkly.SessionReplay;
+
+var ldConfig = Configuration.Builder(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Enabled)
+ .Plugins(new PluginConfigurationBuilder()
+ .Add(new ObservabilityPlugin(
+ new ObservabilityOptions(isEnabled: true, serviceName: "maui-sample-app"),
+ new SessionReplayOptions(isEnabled: true))))
+ .Build();
+var client = LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));
+```
+
#### Standalone (without a LaunchDarkly client)
If you are not using the LaunchDarkly client, pass your mobile key directly. This boots the native observability stack without registering a plugin:
@@ -162,7 +191,7 @@ LDObserve.RecordLog(
);
```
-Supported severity levels: `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`.
+Supported severity levels: `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`, each with numbered variants (for example `Info2` to `Info4`) matching the OpenTelemetry severity numbers.
##### Logs with Span Context
diff --git a/sdk/@launchdarkly/mobile-dotnet/android/LDObserve.Android.Binding/LDObserve.Android.Binding.csproj b/sdk/@launchdarkly/mobile-dotnet/android/LDObserve.Android.Binding/LDObserve.Android.Binding.csproj
index 6c90d0f0ed..c786e9ac33 100644
--- a/sdk/@launchdarkly/mobile-dotnet/android/LDObserve.Android.Binding/LDObserve.Android.Binding.csproj
+++ b/sdk/@launchdarkly/mobile-dotnet/android/LDObserve.Android.Binding/LDObserve.Android.Binding.csproj
@@ -33,11 +33,6 @@
-
-
-
-
-
-
+
diff --git a/sdk/@launchdarkly/mobile-dotnet/sample/AppShell.xaml b/sdk/@launchdarkly/mobile-dotnet/sample/AppShell.xaml
index 0ad042f527..c4914eb73f 100644
--- a/sdk/@launchdarkly/mobile-dotnet/sample/AppShell.xaml
+++ b/sdk/@launchdarkly/mobile-dotnet/sample/AppShell.xaml
@@ -10,7 +10,7 @@
Shell.BackgroundColor="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}">
diff --git a/sdk/@launchdarkly/mobile-dotnet/sample/AppShell.xaml.cs b/sdk/@launchdarkly/mobile-dotnet/sample/AppShell.xaml.cs
index 04b167415b..c4897d5ce9 100644
--- a/sdk/@launchdarkly/mobile-dotnet/sample/AppShell.xaml.cs
+++ b/sdk/@launchdarkly/mobile-dotnet/sample/AppShell.xaml.cs
@@ -6,8 +6,23 @@ public AppShell()
{
InitializeComponent();
+ MainContent.Title = BuildTitle();
+
Routing.RegisterRoute(nameof(CreditCardPage), typeof(CreditCardPage));
Routing.RegisterRoute(nameof(NumberPadPage), typeof(NumberPadPage));
Routing.RegisterRoute(nameof(DialogsPage), typeof(DialogsPage));
}
+
+ // MauiSample9 and MauiSample10 share these sources, so the title is what tells the two
+ // builds apart on a device.
+ private static string BuildTitle()
+ {
+#if DEBUG
+ const string configuration = "Debug";
+#else
+ const string configuration = "Release";
+#endif
+ var platform = DeviceInfo.Platform == DevicePlatform.WinUI ? "Windows" : DeviceInfo.Platform.ToString();
+ return $"{platform} · .NET {Environment.Version.Major} · {configuration}";
+ }
}
diff --git a/sdk/@launchdarkly/mobile-dotnet/sample/DialogsPage.xaml.cs b/sdk/@launchdarkly/mobile-dotnet/sample/DialogsPage.xaml.cs
index 00d9c2abf8..35d6b63e0b 100644
--- a/sdk/@launchdarkly/mobile-dotnet/sample/DialogsPage.xaml.cs
+++ b/sdk/@launchdarkly/mobile-dotnet/sample/DialogsPage.xaml.cs
@@ -1,4 +1,8 @@
using CommunityToolkit.Maui.Views;
+#if NET10_0_OR_GREATER
+// Toolkit v12 moved ShowPopupAsync out of CommunityToolkit.Maui.Views.
+using CommunityToolkit.Maui.Extensions;
+#endif
using System.Linq;
using System.Threading;
#if IOS
@@ -236,7 +240,13 @@ private async void OnToolkitPopupSheetClicked(object? sender, EventArgs e)
BackgroundColor = Color.FromArgb("#F2B8B5"),
TextColor = Colors.White
};
+ // CommunityToolkit.Maui replaced Popup.Close() with CloseAsync() in v12. The .NET 9 build
+ // stays on toolkit v11, which caps Microsoft.Maui.Essentials below 10.0.0.
+#if NET10_0_OR_GREATER
+ closeBtn.Clicked += async (s, args) => await popup.CloseAsync();
+#else
closeBtn.Clicked += (s, _) => popup.Close();
+#endif
popup.Content = new VerticalStackLayout
{
@@ -268,8 +278,12 @@ private async void OnToolkitPopupSheetClicked(object? sender, EventArgs e)
_ = this.ShowPopupAsync(popup);
await AutoClose(async () => {
+#if NET10_0_OR_GREATER
+ await popup.CloseAsync();
+#else
popup.Close();
await Task.CompletedTask;
+#endif
});
}
diff --git a/sdk/@launchdarkly/mobile-dotnet/sample/MauiSample10.csproj b/sdk/@launchdarkly/mobile-dotnet/sample/MauiSample10.csproj
new file mode 100644
index 0000000000..0f51bc7794
--- /dev/null
+++ b/sdk/@launchdarkly/mobile-dotnet/sample/MauiSample10.csproj
@@ -0,0 +1,155 @@
+
+
+
+
+ obj\MauiSample10\
+ bin\MauiSample10\
+
+
+
+
+
+ net10.0-android;net10.0-ios
+
+ Exe
+ MauiSample9
+ true
+ true
+ enable
+ enable
+
+ true
+
+
+ $(DefaultItemExcludes);bin\**;obj\**
+
+
+ MauiSample10
+
+
+ com.companyname.mauisample10
+
+
+ 1.0
+ 1
+
+ 15.0
+ 23.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+ true
+
+ false
+
+
+
+
+
diff --git a/sdk/@launchdarkly/mobile-dotnet/sample/MauiSample9.csproj b/sdk/@launchdarkly/mobile-dotnet/sample/MauiSample9.csproj
index 26158f44ff..7625969d6d 100644
--- a/sdk/@launchdarkly/mobile-dotnet/sample/MauiSample9.csproj
+++ b/sdk/@launchdarkly/mobile-dotnet/sample/MauiSample9.csproj
@@ -84,6 +84,13 @@
true
+
+
+ true
+
+
true