3.12.1.2
What's changed
Binding-only release. The native SDK is unchanged — still
dd-sdk-android 3.12.1 — and so are
the package IDs, namespaces and every existing signature. Upgrading is a version bump.
Everything here is an addition to the convenience layer, and every one exists because the
generated binding leaves a member technically reachable but effectively unusable from C#.
The two that matter most are blocked by Kotlin function types, which bind as interfaces rather
than delegates: you cannot pass a lambda, a method group, or an Action<>, so calling them means
declaring a Java.Lang.Object subclass and returning null for Kotlin's Unit. That is a lot of
ceremony for a header setter and a session id.
Package versions are
<dd-sdk-android version>.<binding revision>.3.12.1.2is
dd-sdk-android3.12.1, binding revision2. The fourth component belongs to this repository and
advances when the bindings or packaging change while the native artifacts stay put.
Added
Trace header injection — DatadogPropagation.Inject
The one that matters most. Distributed tracing is the reason most apps enable Trace at all, and this
was the wall in front of it:
fun <C> inject(context: DatadogSpanContext, carrier: C, setter: (C, String, String) -> Unit)That trailing lambda binds as Kotlin.Jvm.Functions.IFunction3. Getting it wrong does not fail
loudly — the request simply goes out with no trace headers, and the trace stops at the app.
var span = GlobalDatadogTracer.Get ().BuildSpan ("http.request").Start ();
foreach (var header in GlobalDatadogTracer.Get ().Propagate ().Inject (span.Context ()))
request.Headers.TryAddWithoutValidation (header.Key, header.Value);Three forms: one returning a new dictionary, one writing into a dictionary you own, and one taking
an Action<string, string> for carriers that are not dictionaries — an HttpRequestMessage, gRPC
metadata, a message envelope.
extract is deliberately not wrapped. Its signature nests a second Kotlin lambda — the SDK
hands you a visitor you call per header, whose Boolean return governs whether iteration continues
— and that contract is not documented anywhere the binding can see. Guessing it would produce
something that silently reads one header and stops. Extraction is also the rare direction for a
mobile app, which receives responses rather than serving requests.
Span outcome and identity — DatadogSpanExtensions
span.SetError (exception); // or SetError (kind, message, stack)
var traceId = span.GetTraceId (); // 32 lowercase hex
var spanId = span.GetSpanId (); // decimalSetError exists because addThrowable takes a java.lang.Throwable, which a .NET exception is
not — so the managed type, message and stack have to be set as separate fields for any of them to
reach Datadog. setError additionally takes a java.lang.Boolean, so even the flag needs boxing.
GetTraceId and GetSpanId render the ids the way dd-sdk-android's own DatadogInterceptor
does when it links a RUM resource to its APM trace: DatadogTraceId.toHexString() and
String.valueOf(long). The asymmetry — hex for one, decimal for the other — is Datadog's wire
format rather than a choice, and matching it exactly is what makes the correlation work.
toHexString() is toHexStringPadded(…, 32) on both DD128bTraceId and DD64bTraceId, so the
result is always 32 characters; a 64-bit id is the low half behind sixteen zeros, never a
16-character string.
Session id — RumMonitorExtensions.GetCurrentSessionIdAsync
var sessionId = await GlobalRumMonitor.Get ().GetCurrentSessionIdAsync ();getCurrentSessionId takes a kotlin.jvm.functions.Function1, so reading a value most apps want in
order to put it on a support ticket previously required an IFunction1 implementation returning
null for Kotlin's Unit.
Single-value attributes
DatadogAttributes.ToJava (value, key) is now public. RumMonitor.addAttribute,
addFeatureFlagEvaluation and Logger.addAttribute all take a bare java.lang.Object while
DatadogAttributes only exposed the map form, so a caller either hand-wrapped the value — which is
exactly what DatadogAttributes exists to avoid — or round-tripped a one-element dictionary.
monitor.AddAttribute ("cart.id", cartId);
monitor.AddFeatureFlagEvaluation ("new-checkout", true);
logger.AddAttribute ("tenant", tenantId);Resource overloads
StartResource, StopResource and two StopResourceWithError forms on RumMonitorExtensions,
taking int?/long? instead of Java.Lang.Integer/Java.Lang.Long, and an Exception overload.
The exception form also guards a trap the C# signature does not show: stackTrace is String in
Kotlin, not String?, so passing null reaches Java's own null check and throws — unlike
errorType beside it, and unlike addErrorWithStacktrace, both of which are genuinely nullable.
One thing deliberately not added
An earlier draft added a BuildSpan(string) overload, on the grounds that 3.x DatadogTracer
declares buildSpan(CharSequence) and callers were writing
BuildSpan(new Java.Lang.String(name)). That was wrong: the generator already emits
IDatadogTracerExtensions.BuildSpan(IDatadogTracer, string) in the same namespace, and a second one
made the call ambiguous. If you have been wrapping the operation name, you only need the using:
using Com.Datadog.Android.Trace.Api.Tracer;Documentation
build/packages.tsv now records what is not bound, and why — all fourteen dd-sdk-android*
artifacts Datadog publishes that this repository does not wrap, each with a reason. Datadog ships
27; thirteen are bound. The rest fall into four groups: Kotlin-ecosystem integrations whose idioms
have no C# meaning (-ktx, -rx, the two coroutine modules), instrumentation for Java libraries a
.NET app does not use (-glide, -coil, -fresco, -timber, -sqldelight), things that are not
libraries (-gradle-plugin, the BOM), and three worth revisiting — -compose, -tv and the two
OpenTelemetry modules.
The file already listed what is bound; the point of the addition is that "is X missing on purpose?"
now has an answer in the repository rather than in someone's memory.
Tests
94 package-layout tests pass, and the on-emulator suite grows from 13 checks to 17, all
running against the packed packages.
Until now this suite enabled Trace and stopped there — EnablesTrace registered a tracer and never
started a span — so the tracing path was configured and never driven. That is now closed:
- A span is started, tagged, errored, logged and finished. Its ids are asserted by shape
rather than for non-emptiness: 32 lowercase hex characters and not all zeros for the trace, decimal
for the span. - Headers are injected and cross-checked two ways: the id must equal the
traceparentW3C value
across all 128 bits — derived independently ofGetTraceId, so a second opinion rather than a
restatement — and must end with the low 64 bits the Datadog header carries in decimal. The
delegate form is driven too, and must write the same number of headers as the dictionary form. - The single-value attribute overloads are driven on RUM, feature flags and a logger, with
ToJavaasserted directly. - The session id is read through
GetCurrentSessionIdAsyncand asserted non-null; a null means
the Kotlin callback never fired, which is the failure theTaskwrapper exists to surface.
The harness gained async support to do the last of those.
Sample
samples/DatadogNet.Android.Example was still largely the MAUI project template — a hovercraft, a
click counter, and two Datadog calls. It is now the same shape as the iOS sample: sections for RUM,
Trace and Logs, and an on-screen activity log so it is useful without a Datadog account to send to.
The new Trace section traces an outgoing HttpRequestMessage end to end and records a failed
span.
It also had a real bug worth not shipping in a sample: OnDisappearing disposed the view scope and
then immediately started a new view, leaving one open for the rest of the session — collecting
every action and error that followed. Fixed.
Upgrading from 3.12.1.1
-<PackageReference Include="DatadogNet.RUM.Android" Version="3.12.1.1" />
+<PackageReference Include="DatadogNet.RUM.Android" Version="3.12.1.2" />Nothing is removed or renamed, and the native .aar files are byte-for-byte the same. All thirteen
packages move together, as they depend on each other at an exact version.
Full changelog: v3.12.1.1...v3.12.1.2
Packages
Bound against dd-sdk-android 3.12.1, targeting net8.0-android34.0, net9.0-android35.0 and net10.0-android36.0.
The first three components of
3.12.1.2are the dd-sdk-android version; the fourth is this
repository's binding revision, which advances when the bindings or packaging change while
the native binaries stay put.
| Package | Wraps | NuGet |
|---|---|---|
DatadogNet.Internal.Android |
dd-sdk-android-internal |
3.12.1.2 |
DatadogNet.Core.Android |
dd-sdk-android-core |
3.12.1.2 |
DatadogNet.Logs.Android |
dd-sdk-android-logs |
3.12.1.2 |
DatadogNet.TraceApi.Android |
dd-sdk-android-trace-api |
3.12.1.2 |
DatadogNet.TraceInternal.Android |
dd-sdk-android-trace-internal |
3.12.1.2 |
DatadogNet.Trace.Android |
dd-sdk-android-trace |
3.12.1.2 |
DatadogNet.RUM.Android |
dd-sdk-android-rum |
3.12.1.2 |
DatadogNet.SessionReplay.Android |
dd-sdk-android-session-replay |
3.12.1.2 |
DatadogNet.SessionReplayMaterial.Android |
dd-sdk-android-session-replay-material |
3.12.1.2 |
DatadogNet.SessionReplayCompose.Android |
dd-sdk-android-session-replay-compose |
3.12.1.2 |
DatadogNet.Ndk.Android |
dd-sdk-android-ndk |
3.12.1.2 |
DatadogNet.WebView.Android |
dd-sdk-android-webview |
3.12.1.2 |
DatadogNet.OkHttp.Android |
dd-sdk-android-okhttp |
3.12.1.2 |
dotnet add package DatadogNet.RUM.Android --version 3.12.1.2
Licensing
The binding code is MIT; the bundled native binaries are Apache-2.0, as built and
published by Datadog. Each package ships both texts under licenses/.