Mapping File Uploads for MAUI Android
When building MAUI Android apps with code obfuscation (ProGuard/R8) or native code (NDK), you’ll need to upload mapping files to Datadog to get readable stack traces in RUM Error Tracking.
Why Mapping Files are Needed
- ProGuard/R8 Mapping: Deobfuscates minified class/method names in crash reports
- NDK Symbol Files: Resolves native crash stack traces from C/C++ code
Prerequisites: Enable R8 Code Shrinking
R8 mapping files are only generated when code shrinking is enabled. Add this to your MAUI Android project’s .csproj file:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<!-- Enable R8 code shrinker -->
<AndroidEnableR8>True</AndroidEnableR8>
<AndroidLinkTool>r8</AndroidLinkTool>
<!-- Link mode: SdkOnly (safer) or Full (more aggressive shrinking) -->
<AndroidLinkMode>Full</AndroidLinkMode>
<!-- Optional: Custom ProGuard rules file -->
<!-- <AndroidProguardConfig>proguard.cfg</AndroidProguardConfig> -->
</PropertyGroup>
Link Mode Options:
SdkOnly: Only shrinks SDK assemblies, leaves your code untouched (safer, less aggressive)Full: Shrinks both SDK and your app code (more aggressive, may require custom ProGuard rules)
After enabling R8, build your Release configuration:
dotnet build -c Release -f net9.0-android
The mapping file will be generated at one of these locations:
Primary location (standard MAUI builds):
bin/Release/net9.0-android/mapping.txt
bin/Release/net10.0-android/mapping.txt
Alternative location (intermediate build output):
obj/Release/net9.0-android/lp/map.cache/mapping.txt
obj/Release/net10.0-android/lp/map.cache/mapping.txt
Note:
- The
bin/location is the final output directory and is typically more reliable - Without R8 enabled, no mapping file is generated and stack traces will already be readable (but your APK will be larger)
- If publishing as an Android App Bundle (
.aab) to Google Play, the mapping file is often uploaded automatically
Custom ProGuard Rules (Optional)
If you encounter runtime crashes after enabling R8 (common with reflection-heavy code), you may need custom ProGuard rules.
Create a proguard.cfg file in your project root:
# Keep Datadog SDK classes
-keep class com.datadog.** { *; }
# Keep classes used via reflection
-keep class com.yourcompany.yourapp.models.** { *; }
# Keep MAUI generated code
-keep class crc64** { *; }
-keep class mono.** { *; }
Then reference it in your .csproj:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<AndroidProguardConfig>proguard.cfg</AndroidProguardConfig>
</PropertyGroup>
Common issues requiring ProGuard rules:
- JSON serialization classes being stripped
- Classes accessed via reflection (dependency injection, plugins)
- Third-party SDK classes that R8 incorrectly considers unused
Why the Gradle Plugin Doesn’t Work
The native Android dd-sdk-android-gradle-plugin is not compatible with MAUI projects because:
- MAUI uses MSBuild, not Gradle directly
- Gradle plugin tasks cannot be bound to .NET
- MAUI’s build process wraps Gradle internally
Recommended Approach: datadog-ci CLI
The datadog-ci command-line tool is the recommended way to upload mapping files from MAUI projects.
Which datadog-ci command to use?
| Command | Use Case | MAUI Compatibility |
|---|---|---|
datadog-ci flutter-symbols upload |
Android/iOS symbols (can handle Android mappings, NDK symbols, iOS dSYMs) | ✅ Use for MAUI Android |
datadog-ci dsyms upload |
iOS/macOS dSYM files only | ✅ Use for MAUI iOS |
For MAUI projects, use:
datadog-ci flutter-symbols upload --android-mappingfor Android R8/ProGuard mapping filesdatadog-ci flutter-symbols upload --android-mapping --ndk-symbol-filesfor combined mapping + NDK symbolsdatadog-ci dsyms uploadfor iOS dSYM files (separate documentation needed)
Important: While the command is named flutter-symbols, it works for any Android/iOS app. The key is providing explicit paths and ensuring --service-name, --flavor, and --version exactly match what your app sends to Datadog in RUM events.
Critical: Parameter Matching
⚠️ The upload will silently succeed but symbols won’t be found if parameters don’t match exactly.
Because datadog-ci flutter-symbols can’t automatically determine your app’s metadata, you must ensure these values match exactly:
| Parameter | Must Match | How to Find in MAUI |
|---|---|---|
--service-name |
The serviceName in your Datadog SDK configuration |
Check your Datadog.Initialize() call or defaults to bundle ID |
--flavor |
Your build configuration | Usually release for Release builds, debug for Debug |
--version |
The version sent in RUM events | Usually your ApplicationVersion from .csproj or CFBundleShortVersionString |
Example of checking your service name in code:
// In your MAUI app startup
Datadog.Initialize(
configuration: new DatadogConfiguration(
clientToken: "...",
environment: "prod",
serviceName: "com.mycompany.myapp" // ← This must match --service-name
)
);
If not explicitly set, the default service name is your bundle identifier:
- Android:
com.mycompany.myapp(fromApplicationIdin .csproj) - iOS:
com.mycompany.myapp(fromCFBundleIdentifierin Info.plist)
1. Install datadog-ci
npm install -g @datadog/datadog-ci
2. Set Environment Variables
export DD_API_KEY=<your-api-key>
export DD_SITE=datadoghq.com # or datadoghq.eu, etc.
3. Upload ProGuard/R8 Mapping
After building your Release configuration:
datadog-ci flutter-symbols upload \
--service-name <your-service-name> \
--flavor release \
--version <your-version> \
--android-mapping \
--android-mapping-location <path-to-mapping.txt>
Example:
datadog-ci flutter-symbols upload \
--service-name com.mycompany.myapp \
--flavor release \
--version 1.0.0 \
--android-mapping \
--android-mapping-location bin/Release/net9.0-android/mapping.txt
Mapping file locations (MAUI Android Release builds):
<project>/bin/Release/net9.0-android/mapping.txt (primary)
<project>/bin/Release/net10.0-android/mapping.txt (primary)
<project>/obj/Release/net9.0-android/lp/map.cache/mapping.txt (alternative)
<project>/obj/Release/net10.0-android/lp/map.cache/mapping.txt (alternative)
Critical: The --service-name must exactly match the service name configured in your Datadog MAUI SDK initialization. By default, this is your app’s bundle identifier (e.g., com.mycompany.myapp).
4. Upload NDK Symbol Files
If your MAUI app includes native libraries (C/C++ code via P/Invoke or native bindings):
datadog-ci flutter-symbols upload \
--service-name <your-service-name> \
--flavor release \
--version <your-version> \
--ndk-symbol-files <path-to-symbol-directory>
Example with specific architecture:
datadog-ci flutter-symbols upload \
--service-name com.mycompany.myapp \
--flavor release \
--version 1.0.0 \
--ndk-symbol-files obj/Release/net9.0-android/android/assets/obj/local/arm64-v8a
NDK symbols locations in MAUI Android builds:
For native libraries compiled with debug symbols:
<project>/obj/Release/net9.0-android/android/assets/obj/local/arm64-v8a/
<project>/obj/Release/net9.0-android/android/assets/obj/local/armeabi-v7a/
<project>/obj/Release/net9.0-android/android/assets/obj/local/x86/
<project>/obj/Release/net9.0-android/android/assets/obj/local/x86_64/
Or for extracted .so files:
<project>/obj/Release/net9.0-android/android/libs/
Note: Most MAUI apps don’t have NDK symbols unless you’re:
- Using native libraries via P/Invoke
- Including third-party native SDKs
- Using Datadog’s NDK crash reporting module
5. Combined Upload (Mapping + NDK)
You can upload both mapping files and NDK symbols in a single command:
datadog-ci flutter-symbols upload \
--service-name com.mycompany.myapp \
--flavor release \
--version 1.0.0 \
--android-mapping \
--android-mapping-location bin/Release/net9.0-android/mapping.txt \
--ndk-symbol-files obj/Release/net9.0-android/android/assets/obj/local/arm64-v8a \
--ndk-symbol-files obj/Release/net9.0-android/android/assets/obj/local/armeabi-v7a
This is the most efficient approach for complete crash symbolication coverage.
Note on multiple architectures: You can specify --ndk-symbol-files multiple times to upload symbols for different ABIs (arm64-v8a, armeabi-v7a, x86, x86_64).
Troubleshooting MAUI Build Output Paths
If mapping.txt doesn’t exist
Problem: mapping.txt file is not generated after building.
Solutions:
-
Verify R8 is enabled in your
.csproj:grep -A3 "AndroidEnableR8" YourProject.csproj # Should show: <AndroidEnableR8>True</AndroidEnableR8> -
Build in Release configuration:
dotnet build -c Release -f net9.0-androidDebug builds typically don’t enable code shrinking.
-
Check build output for R8 execution:
dotnet build -c Release -f net9.0-android -v detailed | grep -i "r8" # Should show R8 task execution -
Verify AndroidLinkMode:
AndroidLinkMode=None→ No mapping file generatedAndroidLinkMode=SdkOnlyorFull→ Mapping file generated
If the default paths don’t work, here’s how to find your files:
Finding R8/ProGuard Mapping Files
Search for mapping.txt in your build output:
find . -name "mapping.txt" -type f | grep -E "(Release|release)"
Common MAUI Android locations (in order of preference):
bin/Release/net9.0-android/mapping.txt(primary, final build output)bin/Release/net10.0-android/mapping.txt(primary, final build output)obj/Release/net9.0-android/lp/map.cache/mapping.txt(intermediate)obj/Release/net10.0-android/lp/map.cache/mapping.txt(intermediate)
Finding NDK Symbol Files
Search for .so files with debug symbols:
find . -name "*.so" -type f | grep -E "(obj|libs)"
Verify Files Are Valid
Check mapping file has content:
head -5 mapping.txt
# Should show obfuscation mappings like:
# com.myapp.MainActivity -> a:
# void onCreate(android.os.Bundle) -> a
Check NDK symbols have debug info:
file libmylib.so
# Should show: ELF 64-bit LSB shared object, ARM aarch64, not stripped
Alternative: MSBuild Integration
You can integrate the upload into your build process by adding a custom MSBuild target:
<!-- In your .csproj file -->
<Target Name="UploadMappingToDatadog" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<Exec Command="datadog-ci flutter-symbols upload --service-name com.mycompany.myapp --flavor release --version $(ApplicationVersion) --android-mapping --android-mapping-location $(OutputPath)mapping.txt" />
</Target>
Note: $(OutputPath) resolves to bin/Release/net9.0-android/ or similar, depending on the target framework.
Prerequisites:
datadog-cimust be installed globally or in your CI environment- Set
DD_API_KEYandDD_SITEenvironment variables
CI/CD Integration Examples
GitHub Actions
- name: Upload Mapping Files to Datadog
env:
DD_API_KEY: ${{ secrets.DD_API_KEY }}
DD_SITE: datadoghq.com
run: |
npm install -g @datadog/datadog-ci
datadog-ci flutter-symbols upload \
--service-name com.mycompany.myapp \
--flavor release \
--version ${{ github.ref_name }} \
--android-mapping \
--android-mapping-location MyApp/obj/Release/net9.0-android/lp/map.cache/mapping.txt
Azure Pipelines
- task: Npm@1
inputs:
command: "custom"
customCommand: "install -g @datadog/datadog-ci"
- script: |
datadog-ci flutter-symbols upload \
--service-name com.mycompany.myapp \
--flavor release \
--version $(Build.BuildNumber) \
--android-mapping \
--android-mapping-location $(Build.SourcesDirectory)/MyApp/obj/Release/net9.0-android/lp/map.cache/mapping.txt
env:
DD_API_KEY: $(DD_API_KEY)
DD_SITE: datadoghq.com
Manual Upload via Datadog UI
If automation isn’t possible, you can manually upload mapping files:
- Go to Datadog RUM → Error Tracking
- Navigate to Source Maps/Mappings section
- Upload your mapping.txt file with the corresponding version