Skip to main content

Screen Name Mapping

Screen name mapping lets you show friendly, human-readable screen names in your Paylisher dashboard instead of raw class names. Reports become readable for non-technical teammates, and you can rename screens without touching your code — just edit a JSON file.

Raw class nameMapped screen name
HomeFragmentHome
ProfileFragmentProfile
SettingsActivitySettings

Without mapping, the SDK still reports a clean default name (it strips the Fragment suffix, so HomeFragment becomes Home). Mapping is what lets you override that default with any label you like — including localized or product-specific names such as Ana Sayfa or Shopping Cart.


How it works

  1. On startup, the SDK prepares a screen mapper as part of PaylisherAndroid.setup().
  2. The first time a screen is captured, the mapper lazily loads paylisher_screens.json from your app's assets/ folder, then caches it for the rest of the process.
  3. When the user navigates to a screen, the SDK looks up the screen's fully qualified class name in the mapping.
  4. If a match is found, the mapped name is used. Otherwise the default name is used.
  5. The resulting name is sent with the $screen event and shown in the dashboard.

The mapper is created for you automatically — there is no extra setup call. If paylisher_screens.json is missing, mapping is simply skipped and default names are used, so adding the file is completely optional and non-breaking.

Because the file is read once per process and then cached, changes to paylisher_screens.json only take effect after the app is restarted.


1. Add the mapping file

Create the file at:

app/src/main/assets/paylisher_screens.json

If the assets folder doesn't exist yet, create it under app/src/main/. The file must live at the root of assets/ — the SDK looks for paylisher_screens.json directly, so a copy placed in an assets/ subfolder is silently ignored.

The file is a flat JSON object where each key is a fully qualified class name and each value is the display name:

{
"com.myapp.fragments.HomeFragment": "Home",
"com.myapp.fragments.ProfileFragment": "Profile",
"com.myapp.fragments.CartFragment": "Shopping Cart",
"com.myapp.MainActivity": "Home Screen",
"com.myapp.ProductDetailActivity": "Product Detail"
}

Important: Keys must be the fully qualified class name (package + class), not just the simple class name. If mapping doesn't work, this is almost always the cause — see Troubleshooting.

Keep it flat, values as strings. Every value must be a plain string. A nested object or array value will abort loading of the file.

You can map both Fragments and Activities in the same file. Whether Activity mapping actually takes effect depends on your tracking mode (see below).


2. Enable screen tracking

Screen tracking is enabled by default, so no code change is required for the common case. The relevant config options are:

val config = PaylisherAndroidConfig(
apiKey = PAYLISHER_API_KEY,
host = PAYLISHER_HOST,
).apply {
// Fragment screen views (single-activity architecture). true by default.
captureFragmentScreenViews = true

// Activity screen views. true by default, but automatically
// suppressed while Fragment tracking is enabled to avoid duplicates.
captureScreenViews = true
}

PaylisherAndroid.setup(this, config)

Screen tracking installs only when the SDK is initialized with an Application context (the recommended setup, from your Application subclass — see Getting Started).

Fragments vs. Activities

Modern Android apps typically use a single Activity with multiple Fragments, so Fragment tracking is enabled by default. To prevent duplicate $screen events, Activity-level tracking is automatically disabled whenever captureFragmentScreenViews = true.

This has a practical consequence for mapping:

  • Default setup (Fragment tracking on): Your Fragment entries in paylisher_screens.json are used. Activity entries are ignored because Activity tracking is suppressed.
  • Legacy setup (Activity tracking only): Set captureFragmentScreenViews = false and captureScreenViews = true. Now your Activity entries are used.
// Use this only if your app is Activity-based and you want Activity mapping.
config.captureFragmentScreenViews = false
config.captureScreenViews = true

Name resolution order

Fragments

When a Fragment becomes visible (onResume), the SDK resolves its screen name in this order:

  1. Custom transformerfragmentScreenNameTransformer, if you set one (highest priority).
  2. JSON mapping — a match for the Fragment's fully qualified class name in paylisher_screens.json.
  3. Default — the simple class name with the Fragment suffix removed (HomeFragmentHome).

NavHostFragment (the Navigation Component container) is skipped so it never shows up as a screen.

Activities

When Activity tracking is active, the SDK resolves the name in this order:

  1. JSON mapping — a match for the Activity's fully qualified class name.
  2. android:label — the <activity>'s label, if set and different from the application label.
  3. Default — the simple Activity class name (package stripped, e.g. MainActivity).

Alternative: custom transformer (no JSON)

If you'd rather resolve Fragment names in code instead of maintaining a JSON file, provide a fragmentScreenNameTransformer. It takes precedence over the JSON mapping:

config.fragmentScreenNameTransformer = { fragment ->
when (fragment) {
is HomeFragment -> "Home"
is ProfileFragment -> "Profile"
is SettingsFragment -> "Settings"
else -> fragment.javaClass.simpleName
}
}

Because this uses is type checks rather than string class names, it keeps working even after R8/ProGuard obfuscation — see the next section.


R8 / ProGuard (release builds)

JSON mapping relies on matching the fully qualified class name at runtime. When R8/ProGuard obfuscation is enabled (typical for release builds), class names are renamed — com.myapp.HomeFragment may become something like a.b.c — and the lookup no longer matches.

Build typeRuntime class nameMapping result
Debugcom.myapp.HomeFragment✅ Works
Release (R8 on)a.b.c❌ Does not work

You have three options.

Add keep rules to proguard-rules.pro:

# Paylisher screen mapping — preserve Fragment and Activity class names
-keepnames class * extends androidx.fragment.app.Fragment
-keepnames class * extends android.app.Activity
-keepnames class * extends androidx.appcompat.app.AppCompatActivity

-keepnames preserves the class names while still allowing method obfuscation.

Option 2 — Keep only the classes you map

If you don't want to preserve every Fragment/Activity name, keep only the ones referenced in your mapping:

-keepnames class com.myapp.fragments.HomeFragment
-keepnames class com.myapp.fragments.ProfileFragment
-keepnames class com.myapp.MainActivity
-keepnames class com.myapp.ProductDetailActivity

Option 3 — Use a transformer instead

Use fragmentScreenNameTransformer (see above). Because it relies on is type checks rather than string class names, it is unaffected by obfuscation and needs no keep rules.

Verify the release mapping

After a release build, check app/build/outputs/mapping/release/mapping.txt. A preserved class looks like this (name unchanged on both sides):

com.myapp.fragments.HomeFragment -> com.myapp.fragments.HomeFragment

If instead you see the name renamed, JSON mapping won't match:

com.myapp.fragments.HomeFragment -> a.b.c

A typical build.gradle.kts release block:

android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
}

Troubleshooting

SymptomLikely causeFix
Mapping has no effectKey is not the fully qualified class nameUse fragment.javaClass.name (package + class) as the JSON key
Mapping has no effectInvalid/malformed JSONA JSON syntax error fails silently (only logged with debug = true) and falls back to default names — validate the file
Mapping has no effectFile in a subfolderThe file must be at the root: app/src/main/assets/paylisher_screens.json
Some screens map, others don'tMissing entryAdd the class name to the JSON, or accept the default name
Activity names not mappingFragment tracking is onActivity tracking is suppressed by default; set captureFragmentScreenViews = false if your app is Activity-based
Works in debug, not in releaseR8/ProGuard obfuscationAdd keep rules or use a transformer (see above)
Edits to the JSON don't applyFile cachedThe file is read once per process — restart the app

Enable debug logging

Filter Logcat by the Paylisher tag to see the mapper at work. Per-lookup diagnostics (the class name being looked up and the match result) are logged as you navigate. To also see the load summary and integration logs, enable debug:

config.debug = true

Example output:

✅ Loaded 5 screen mappings from paylisher_screens.json
🔍 Fragment class name: com.myapp.fragments.HomeFragment
✅ Using mapped name: Home

Example paylisher_screens.json

{
"com.myapp.fragments.HomeFragment": "Home",
"com.myapp.fragments.ProfileFragment": "Profile",
"com.myapp.fragments.SettingsFragment": "Settings",
"com.myapp.fragments.CartFragment": "Shopping Cart",
"com.myapp.fragments.CheckoutFragment": "Checkout",
"com.myapp.MainActivity": "Home Screen",
"com.myapp.ProductDetailActivity": "Product Detail",
"com.myapp.LoginActivity": "Login"
}

Using the same feature on iOS? See the iOS Screen Name Mapping guide — the concept is identical, but iOS loads the file from the app bundle and keys are class names (module prefix optional).