Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
dcbe7dd
rearrange files
abelonogov-ld Feb 26, 2026
ae11179
Empty session replay plugin
abelonogov-ld Feb 26, 2026
a357362
renaming
abelonogov-ld Feb 26, 2026
fd062ea
empty hooks
abelonogov-ld Feb 26, 2026
605fe0f
EvalHook
abelonogov-ld Feb 26, 2026
c73c0bc
add comment
abelonogov-ld Feb 26, 2026
b5587e7
Android hooks started working
abelonogov-ld Feb 27, 2026
957795b
match to iOS
abelonogov-ld Feb 27, 2026
2e15d53
Merge branch 'main' into andrey/hooks
abelonogov-ld Feb 28, 2026
52e3a3c
remove dependencies
abelonogov-ld Feb 28, 2026
4af8770
0.27.0
abelonogov-ld Mar 1, 2026
858041f
Merge branch 'main' into andrey/hooks
abelonogov-ld Mar 1, 2026
0c55a45
separate start
abelonogov-ld Mar 2, 2026
c4280e2
make SDK35 compilable
abelonogov-ld Mar 2, 2026
8bca813
forgotten dependency
abelonogov-ld Mar 2, 2026
fbf5c1a
Merge branch 'main' into andrey/hooks
abelonogov-ld Mar 2, 2026
08ffef7
can launch
abelonogov-ld Mar 3, 2026
4d48233
working
abelonogov-ld Mar 3, 2026
057dc85
fat working
abelonogov-ld Mar 3, 2026
2fd116b
comment identify stuff
abelonogov-ld Mar 3, 2026
68f9dc7
hook proxy
abelonogov-ld Mar 3, 2026
8f20444
Merge branch 'andrey/hooks' into andrey/android-hooks
abelonogov-ld Mar 3, 2026
5fa9867
demo plugin
abelonogov-ld Mar 3, 2026
1bb1d87
last suggestion
abelonogov-ld Mar 3, 2026
f664bd9
matching versions
abelonogov-ld Mar 3, 2026
0682e76
small fixes
abelonogov-ld Mar 3, 2026
3b9b3e0
3.11
abelonogov-ld Mar 3, 2026
3b7d577
3.13
abelonogov-ld Mar 4, 2026
7e07e89
update
abelonogov-ld Mar 4, 2026
b9ef80d
add xcuserdata to .gitignore for mobile-dotnet SDK
abelonogov-ld Mar 4, 2026
70d6f86
feedback
abelonogov-ld Mar 4, 2026
bf256bb
Merge branch 'main' into andrey/android-hooks
abelonogov-ld Mar 4, 2026
6135e20
format json
abelonogov-ld Mar 4, 2026
7d45595
add tasks.json to .prettierignore
abelonogov-ld Mar 4, 2026
7801dec
launching from local dotnet core
abelonogov-ld Mar 9, 2026
dcff8eb
attirbutes working
abelonogov-ld Mar 11, 2026
76cf0f6
support init attributes
abelonogov-ld Mar 11, 2026
1a08b6c
reverse Android change
abelonogov-ld Mar 11, 2026
1042182
Merge branch 'main' into andrey/init-attributes
abelonogov-ld Mar 11, 2026
e9a3d7e
forgotten files
abelonogov-ld Mar 11, 2026
f17ae86
Remove build pattern for options
abelonogov-ld Mar 11, 2026
194cfba
move file
abelonogov-ld Mar 11, 2026
4372403
Merge branch 'main' into andrey/recordlog
abelonogov-ld Mar 11, 2026
655adba
NativeHookExporter
abelonogov-ld Mar 12, 2026
65d1f0d
comment identify hook methods
abelonogov-ld Mar 12, 2026
6cc40ab
async LDNative.Start
abelonogov-ld Mar 12, 2026
15e2e6d
Dict fix
abelonogov-ld Mar 12, 2026
24acfe4
project changes
abelonogov-ld Mar 12, 2026
c563e0a
Revert "async LDNative.Start"
abelonogov-ld Mar 12, 2026
b774a13
demo 0.52.0
abelonogov-ld Mar 12, 2026
c9f8e5f
to only local nuget
abelonogov-ld Mar 13, 2026
26522e7
Revert "comment identify hook methods"
abelonogov-ld Mar 13, 2026
8e4f3eb
log record
abelonogov-ld Mar 13, 2026
a410930
metrics
abelonogov-ld Mar 13, 2026
edad7da
metrics fixed
abelonogov-ld Mar 13, 2026
e7da451
Dialogs
abelonogov-ld Mar 13, 2026
e3268b1
5.7.0 Client
abelonogov-ld Mar 14, 2026
5145c71
To local C# SDK Client
abelonogov-ld Mar 15, 2026
205e2a3
Revert "To local C# SDK Client"
abelonogov-ld Mar 15, 2026
c09555e
move
abelonogov-ld Mar 15, 2026
9da85c4
wip
abelonogov-ld Mar 18, 2026
0ee0267
wakeup trying
abelonogov-ld Mar 18, 2026
9cf156a
comment debug functionality
abelonogov-ld Mar 18, 2026
857636b
0.4.1
abelonogov-ld Mar 18, 2026
ee128ae
Refactor to SessionReplay
abelonogov-ld Mar 19, 2026
9f888c8
Refactor to be close to iOS and MAUI integration hook
abelonogov-ld Mar 19, 2026
abac43e
remove printing in system
abelonogov-ld Mar 19, 2026
a91fcc1
remove printing in system
abelonogov-ld Mar 19, 2026
271f22f
remove printing in system
abelonogov-ld Mar 19, 2026
a56660e
Android MAUI update
abelonogov-ld Mar 19, 2026
14fb38c
Dotnet MAUI update
abelonogov-ld Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,7 @@ private fun MainScreen(viewModel: ViewModel, innerPadding: PaddingValues) {
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
Text(
text = "Masking",
style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold),
modifier = Modifier.padding(bottom = 8.dp)
)
SessionReplayHeader()
HorizontalDivider(modifier = Modifier.padding(bottom = 16.dp))

MaskingButtons()
Expand All @@ -131,8 +127,6 @@ private fun MainScreen(viewModel: ViewModel, innerPadding: PaddingValues) {
)
HorizontalDivider(modifier = Modifier.padding(bottom = 16.dp))

SessionReplayToggle()

IdentifyButtons(viewModel = viewModel)

InstrumentationButtons(viewModel = viewModel)
Expand All @@ -146,19 +140,19 @@ private fun MainScreen(viewModel: ViewModel, innerPadding: PaddingValues) {
}

@Composable
private fun SessionReplayToggle() {
private fun SessionReplayHeader() {
var isSessionReplayEnabled by rememberSaveable { mutableStateOf(true) }

Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Session Replay",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold)
)
Switch(
checked = isSessionReplayEnabled,
Expand Down Expand Up @@ -295,7 +289,7 @@ private fun MaskingButtons() {
)

MaskingRow(
name = "Check",
name = "Dialogs",
ctx = context,
activity1 = XMLMaskingActivity::class.java,
activity2 = ComposeMaskingActivity::class.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class BenchmarkExecutor {
}

val exportDiffManager = ExportDiffManager(compression = method, scale = 1f)
val eventGenerator = RRWebEventGenerator(canvasDrawEntourage = 300)
val eventGenerator = RRWebEventGenerator(canvasDrawEntourage = 300, title = "benchmark")
val json = Json
var bytes = 0
var isFirst = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ open class BaseApplication : Application() {
.anonymous(true)
.build()

LDClient.init(this@BaseApplication, ldConfig, context)
LDClient.init(this@BaseApplication, ldConfig, context, 1)
val anonContext = LDContext.builder(ContextKind.DEFAULT, "anonymous-userkey")
.anonymous(true)
.build()

//LDClient.get().identify(anonContext)
Copy link

Choose a reason for hiding this comment

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

Unused variable and commented-out code committed

Low Severity

The anonContext variable is created but never used because the LDClient.get().identify(anonContext) call on line 96 is commented out. This looks like leftover debugging or experimentation code that was accidentally included in the commit.

Fix in Cursor Fix in Web


telemetryInspector = observabilityPlugin.getTelemetryInspector()

if (testUrl == null) {
Expand Down
190 changes: 180 additions & 10 deletions sdk/@launchdarkly/mobile-dotnet/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,177 @@
# LaunchDarkly Session Replay for .NET MAUI
# LaunchDarkly Observability SDK for .NET MAUI

The LaunchDarkly Session Replay SDK for .NET MAUI allows you to capture user interactions and screen recordings to understand how users interact with your application.
The LaunchDarkly Observability SDK for .NET MAUI provides automatic and manual instrumentation for your mobile application, including metrics, logs, error reporting, and session replay.

## Early Access Preview

**NB: APIs are subject to change until a 1.x version is released.**

## Features

### Automatic Instrumentation

The .NET MAUI observability plugin automatically instruments:
- **HTTP Requests**: Outgoing HTTP requests
- **Crash Reporting**: Automatic crash reporting and stack traces
- **Feature Flag Evaluations**: Evaluation events added to your spans
- **Session Management**: User session tracking and background timeout handling

## Prerequisites

* **.NET 9.0** or higher is required.
* MAUI support for **iOS** and **Android**.

## Getting Started
## Example Application

To enable Session Replay, you need to configure both the `ObservabilityPlugin` and `SessionReplayPlugin` when initializing the LaunchDarkly client.
A complete example application is available in the [sample](./sample) directory.

### Configure Session Replay
## Usage

In your `MauiProgram.cs` (or wherever you initialize your application), register the plugins via `LdClient`:
### Basic Setup

In your `MauiProgram.cs` (or wherever you initialize your application), register the `ObservabilityPlugin` via `LdClient`:

```csharp
using LaunchDarkly.SessionReplay;
using LaunchDarkly.Observability;
using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Client;
using LaunchDarkly.Sdk.Client.Integrations;

public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();

// ... other configuration ...

var mobileKey = "your-mobile-key";

var ldConfig = Configuration.Builder(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Enabled)
.Plugins(new PluginConfigurationBuilder()
.Add(new ObservabilityPlugin(new ObservabilityOptions(
isEnabled: true,
serviceName: "maui-sample-app"
)))
).Build();

var context = Context.New("maui-user-key");
var client = LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));

return builder.Build();
}
}
```

### Recording Observability Data

After initialization of the LaunchDarkly client, use `LDObserve` to record metrics, logs, and errors:

```csharp
using LaunchDarkly.Observability;

// Record metrics
LDObserve.RecordMetric("user_actions", 1.0);
LDObserve.RecordCount("api_calls", 1.0);
LDObserve.RecordIncr("page_views", 1.0);
LDObserve.RecordHistogram("response_time", 150.0);
LDObserve.RecordUpDownCounter("active_connections", 1.0);

// Record logs with severity and optional attributes
LDObserve.RecordLog(
"User performed action",
LDObserve.Severity.Info,
new Dictionary<string, object?>
{
{ "user_id", "12345" },
{ "action", "button_click" }
}
);

// Record errors with an optional cause
LDObserve.RecordError("Something went wrong", "The underlying cause of the error.");
```

#### Metrics

| Method | Description |
|---|---|
| `RecordMetric(name, value)` | Record a gauge metric |
| `RecordCount(name, value)` | Record a count metric |
| `RecordIncr(name, value)` | Record an incremental counter metric |
| `RecordHistogram(name, value)` | Record a histogram metric |
| `RecordUpDownCounter(name, value)` | Record an up-down counter metric |

#### Logs

Use `RecordLog` to emit structured log records with a severity level and optional attributes:

```csharp
LDObserve.RecordLog(
"Checkout completed",
LDObserve.Severity.Info,
new Dictionary<string, object?>
{
{ "order_id", "ORD-9876" },
{ "total", 42.99 }
}
);
```

Supported severity levels: `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`.

#### Errors

Use `RecordError` to capture error events. The optional second parameter provides the underlying cause:

```csharp
LDObserve.RecordError("Payment failed", "Timeout connecting to payment gateway.");
```

### Identifying Users

Use the LaunchDarkly client to identify or switch user contexts. This ties observability data to the correct user:

```csharp
using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Client;

// Single context
var userContext = Context.Builder("user-key")
.Name("Bob Bobberson")
.Build();
await LdClient.Instance.IdentifyAsync(userContext);

// Multi-context
var userContext = Context.Builder("user-key")
.Name("Bob Bobberson")
.Build();
var deviceContext = Context.Builder(ContextKind.Of("device"), "iphone")
.Name("iphone")
.Build();
var multiContext = Context.MultiBuilder()
.Add(userContext)
.Add(deviceContext)
.Build();
LdClient.Instance.Identify(multiContext, TimeSpan.FromSeconds(5));

// Anonymous context
var anonContext = Context.Builder("anonymous-key")
.Anonymous(true)
.Build();
LdClient.Instance.Identify(anonContext, TimeSpan.FromSeconds(5));
```

## Session Replay

Session Replay captures user interactions and screen recordings to help you understand how users interact with your application. To enable Session Replay, add the `SessionReplayPlugin` alongside the `ObservabilityPlugin`:

```csharp
using LaunchDarkly.SessionReplay;
using LaunchDarkly.Observability;
using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Client;
using LaunchDarkly.Sdk.Client.Integrations;

public static class MauiProgram
{
Expand Down Expand Up @@ -47,15 +199,15 @@ public static class MauiProgram
)))
).Build();

var context = LaunchDarkly.Sdk.Context.New("maui-user-key");
var context = Context.New("maui-user-key");
var client = LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));

return builder.Build();
}
}
```

## Privacy Options
### Privacy Options

You can control what information is captured during a session using `PrivacyOptions`:

Expand All @@ -64,7 +216,7 @@ You can control what information is captured during a session using `PrivacyOpti
* `MaskLabels`: (Default: `false`) Masks all text labels.
* `MaskImages`: (Default: `false`) Masks all images.

## Manual Masking
### Manual Masking

You can manually mask or unmask specific UI components using the provided extension methods on any MAUI `View`.

Expand All @@ -77,3 +229,21 @@ mySensitiveView.LDMask();
// Unmask a specific view
myPublicView.LDUnmask();
```

## Contributing

We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](../../CONTRIBUTING.md) for instructions on how to contribute to this SDK.

## About LaunchDarkly

* LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
* Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
* Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
* Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
* Grant access to certain features based on user attributes, like payment plan (eg: users on the 'gold' plan get access to more features than users in the 'silver' plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
* LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
* Explore LaunchDarkly
* [launchdarkly.com](https://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information
* [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides
* [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation
* [launchdarkly.com/blog](https://launchdarkly.com/blog/ "LaunchDarkly Blog Documentation") for the latest product updates
Loading
Loading