
Capture notification payload snapshots from apps and inspect history, metadata, raw JSON; includes searchable filters, FCM token display, persistent debug notification, copy/share actions.
Android notification payload inspector for debug builds.
Dingtakes its name from the short sound of a notification arriving—a small cue that something happened and is ready to inspect.
Ding captures notification-related payload snapshots from real apps and provides a debug UI for inspecting them.
See notification history at a glance, then drill into capture metadata and raw payloads when something looks wrong.
| Message List | Filtered Actions | Settings |
![]() |
![]() |
![]() |
| Overview | Raw JSON | Dark Mode |
![]() |
![]() |
![]() |
The published Maven artifacts share version 0.5.1.
Kotlin Multiplatform support is being added incrementally. The shared ding-core module normalizes Android payloads and Apple userInfo and provides an Apple capture façade with process-local or Room-backed storage. The existing Android coordinates and Ding.capture(...) API remain unchanged. Native Apple distribution uses a static XCFramework through Swift Package Manager starting with 0.5.0. See the KMP/iOS research note.
Initial supported capture paths:
RemoteMessage
Ding UI:
Open Ding shortcut with a bell icon on the host appChoose the channel that matches where Ding is called:
| Consumer | Channel | Package |
|---|---|---|
| Android app with the debug inspector UI | Maven Central |
ding and ding-noop
|
| Shared Kotlin in a KMP project | Maven Central | ding-core |
| Native iOS app calling Ding from Swift | Swift Package Manager |
Ding XCFramework product, starting with 0.5.0
|
These are alternative integration paths, not three required dependencies. A KMP app that calls ding-core from shared Kotlin does not also need the Swift package. Add SwiftPM only when native Swift code imports Ding directly.
dependencies {
debugImplementation("io.github.easyhooon:ding:0.5.1")
releaseImplementation("io.github.easyhooon:ding-noop:0.5.1")
}ding supports Android 9 (API 28) and newer and resolves ding-core transitively. Android applications should not add the core artifact or its platform variants separately.
Add the shared core only when capturing and processing notification snapshots from KMP source sets:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.easyhooon:ding-core:0.5.1")
}
}
}Gradle selects the Android or Apple platform variant automatically. Do not declare artifacts such as ding-core-android or ding-core-iosarm64 directly. A KMP application's Android app module can still use the ding / ding-noop configuration above to include the inspector UI only in debug builds.
Native Apple hosts that call Ding directly from Swift can select File > Add Package Dependencies in Xcode and enter:
https://github.com/easyhooon/ding
Or declare it in Package.swift starting with the first remote SwiftPM release, 0.5.0:
dependencies: [
.package(
url: "https://github.com/easyhooon/ding.git",
from: "0.5.0"
),
]Add the Ding product to the iOS target, then import Ding from the notification callback that forwards the original APNs userInfo. SwiftPM downloads the versioned Ding.xcframework.zip from the matching GitHub Release and verifies it against the checksum in Package.swift.
Version 0.1.0 was published under the retired notification-inspector coordinates. Ding 0.2.0 moves to new Maven coordinates, the io.github.easyhooon.ding Kotlin package, and the Ding public API before external adoption.
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Ding.capture(this, remoteMessage)
}Ding cannot capture an FCM message through onMessageReceived while the Android app is in the background if the message contains a notification block. In that state, the FCM SDK handles the notification and delivers it directly to the system tray instead of invoking FirebaseMessagingService.onMessageReceived, so the call to Ding.capture(...) never runs.
The same limitation applies to messages containing both notification and data: while the app is in the background, the notification is delivered to the system tray and the data payload is made available through the launcher Activity intent after the user taps the notification. If background capture through Ding is required, send a data-only FCM message and create the user-visible notification from onMessageReceived. See Firebase's official documentation on receiving messages in Android apps and FCM message types.
RemoteMessage does not expose the registration token targeted by the sender. Keep Ding's latest-token cache synchronized from FirebaseMessagingService.onNewToken:
override fun onNewToken(token: String) {
Ding.updateFcmToken(this, token)
}Also refresh the cache from FirebaseMessaging.getToken() at app startup so a restored process does not depend on receiving a new callback:
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
Ding.updateFcmToken(applicationContext, token)
}The token is persisted by the debug implementation; subsequent two-argument capture() calls automatically attach the latest value to each new snapshot. Historical snapshots keep their original token when it changes.
If the host already has the token at capture time, the explicit overload records it and also refreshes the latest-token cache:
Ding.capture(
context = this,
remoteMessage = remoteMessage,
fcmToken = latestFcmToken,
)Messages captured before a token is registered show Not captured in the FCM token section. The stored value is host-supplied context, not proof that the sender targeted that token; topic and condition messages may not target one registration token directly.
Ding.captureNotification(
context = context,
source = "notification-test",
notificationId = notificationId,
title = title,
body = body,
data = mapOf("thread-id" to threadId),
)The debug implementation initializes automatically when the host app starts. Long-press the host app icon and select Open Ding, or tap the persistent Ding notification, to open the captured payload list. The library does not add a separate launcher icon.
The persistent notification is enabled by default. It only shows the captured count and latest category. Disable it when the notification entry point is not wanted:
Ding.setPersistentNotificationEnabled(context, enabled = false)Call the same API with enabled = true to restore it. The explicit choice persists across process restarts. On Android 13 and newer, the host app remains responsible for requesting POST_NOTIFICATIONS at an appropriate time.
ding-core provides a host-owned capture façade for forwarding the original APNs userInfo without installing or replacing notification delegates. Create the persistent store from the Apple source set (iosMain):
val store = PersistentDingCaptureStore.get(
storagePath = dingStoragePath,
)
val ding = DingAppleCapture(store)
ding.updateFcmToken(latestFcmToken)
ding.captureAppleUserInfo(
userInfo = userInfo,
transport = PushTransport.FCM_APNS,
capturePoint = CapturePoint.FOREGROUND,
)FCM and APNs tokens are stored separately, and every snapshot retains the token that was current when it was captured. The host must supply one stable, app-private path ending in .db; the Room-backed store keeps the newest 50 snapshots by default. Reuse the returned store for that path.
InMemoryDingCaptureStore remains available when process-local history is sufficient. The persistent store currently targets callbacks in the main app process. Notification Service Extension/App Group sharing remains a separate follow-up step.
SwiftPM packaging can be verified locally on macOS:
./gradlew :ding-core:packageDingSwiftPM
./gradlew :ding-core:verifyDingLocalSwiftPackageThe first task writes Ding.xcframework.zip and its SwiftPM checksum under ding-core/build/swiftpm/release. The second resolves the local binary target and type-checks Ding's exported API with the Swift compiler. Tagged releases publish that exact archive as a GitHub Release asset and include a root manifest with the matching checksum.
Run the sample module and tap:
Send Local NotificationAdd Demo FCM NotificationsRotate Sample FCM TokenEnable Ding notificationDisable Ding notificationOpen DingThe captured local notification appears under the Local filter. The demo FCM action adds realistic delivery, message, reservation, payment, and activity notifications under FCM.
Notification payloads can contain personal data, authentication material, or internal identifiers. FCM registration tokens identify app instances and are included in raw JSON and exports when captured. Keep Ding on debug builds, review copied or exported content before sharing it, and never attach raw production payloads or tokens to public issues.
Ding is licensed under the Apache License 2.0.
Android notification payload inspector for debug builds.
Dingtakes its name from the short sound of a notification arriving—a small cue that something happened and is ready to inspect.
Ding captures notification-related payload snapshots from real apps and provides a debug UI for inspecting them.
See notification history at a glance, then drill into capture metadata and raw payloads when something looks wrong.
| Message List | Filtered Actions | Settings |
![]() |
![]() |
![]() |
| Overview | Raw JSON | Dark Mode |
![]() |
![]() |
![]() |
The published Maven artifacts share version 0.5.1.
Kotlin Multiplatform support is being added incrementally. The shared ding-core module normalizes Android payloads and Apple userInfo and provides an Apple capture façade with process-local or Room-backed storage. The existing Android coordinates and Ding.capture(...) API remain unchanged. Native Apple distribution uses a static XCFramework through Swift Package Manager starting with 0.5.0. See the KMP/iOS research note.
Initial supported capture paths:
RemoteMessage
Ding UI:
Open Ding shortcut with a bell icon on the host appChoose the channel that matches where Ding is called:
| Consumer | Channel | Package |
|---|---|---|
| Android app with the debug inspector UI | Maven Central |
ding and ding-noop
|
| Shared Kotlin in a KMP project | Maven Central | ding-core |
| Native iOS app calling Ding from Swift | Swift Package Manager |
Ding XCFramework product, starting with 0.5.0
|
These are alternative integration paths, not three required dependencies. A KMP app that calls ding-core from shared Kotlin does not also need the Swift package. Add SwiftPM only when native Swift code imports Ding directly.
dependencies {
debugImplementation("io.github.easyhooon:ding:0.5.1")
releaseImplementation("io.github.easyhooon:ding-noop:0.5.1")
}ding supports Android 9 (API 28) and newer and resolves ding-core transitively. Android applications should not add the core artifact or its platform variants separately.
Add the shared core only when capturing and processing notification snapshots from KMP source sets:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.easyhooon:ding-core:0.5.1")
}
}
}Gradle selects the Android or Apple platform variant automatically. Do not declare artifacts such as ding-core-android or ding-core-iosarm64 directly. A KMP application's Android app module can still use the ding / ding-noop configuration above to include the inspector UI only in debug builds.
Native Apple hosts that call Ding directly from Swift can select File > Add Package Dependencies in Xcode and enter:
https://github.com/easyhooon/ding
Or declare it in Package.swift starting with the first remote SwiftPM release, 0.5.0:
dependencies: [
.package(
url: "https://github.com/easyhooon/ding.git",
from: "0.5.0"
),
]Add the Ding product to the iOS target, then import Ding from the notification callback that forwards the original APNs userInfo. SwiftPM downloads the versioned Ding.xcframework.zip from the matching GitHub Release and verifies it against the checksum in Package.swift.
Version 0.1.0 was published under the retired notification-inspector coordinates. Ding 0.2.0 moves to new Maven coordinates, the io.github.easyhooon.ding Kotlin package, and the Ding public API before external adoption.
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Ding.capture(this, remoteMessage)
}Ding cannot capture an FCM message through onMessageReceived while the Android app is in the background if the message contains a notification block. In that state, the FCM SDK handles the notification and delivers it directly to the system tray instead of invoking FirebaseMessagingService.onMessageReceived, so the call to Ding.capture(...) never runs.
The same limitation applies to messages containing both notification and data: while the app is in the background, the notification is delivered to the system tray and the data payload is made available through the launcher Activity intent after the user taps the notification. If background capture through Ding is required, send a data-only FCM message and create the user-visible notification from onMessageReceived. See Firebase's official documentation on receiving messages in Android apps and FCM message types.
RemoteMessage does not expose the registration token targeted by the sender. Keep Ding's latest-token cache synchronized from FirebaseMessagingService.onNewToken:
override fun onNewToken(token: String) {
Ding.updateFcmToken(this, token)
}Also refresh the cache from FirebaseMessaging.getToken() at app startup so a restored process does not depend on receiving a new callback:
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
Ding.updateFcmToken(applicationContext, token)
}The token is persisted by the debug implementation; subsequent two-argument capture() calls automatically attach the latest value to each new snapshot. Historical snapshots keep their original token when it changes.
If the host already has the token at capture time, the explicit overload records it and also refreshes the latest-token cache:
Ding.capture(
context = this,
remoteMessage = remoteMessage,
fcmToken = latestFcmToken,
)Messages captured before a token is registered show Not captured in the FCM token section. The stored value is host-supplied context, not proof that the sender targeted that token; topic and condition messages may not target one registration token directly.
Ding.captureNotification(
context = context,
source = "notification-test",
notificationId = notificationId,
title = title,
body = body,
data = mapOf("thread-id" to threadId),
)The debug implementation initializes automatically when the host app starts. Long-press the host app icon and select Open Ding, or tap the persistent Ding notification, to open the captured payload list. The library does not add a separate launcher icon.
The persistent notification is enabled by default. It only shows the captured count and latest category. Disable it when the notification entry point is not wanted:
Ding.setPersistentNotificationEnabled(context, enabled = false)Call the same API with enabled = true to restore it. The explicit choice persists across process restarts. On Android 13 and newer, the host app remains responsible for requesting POST_NOTIFICATIONS at an appropriate time.
ding-core provides a host-owned capture façade for forwarding the original APNs userInfo without installing or replacing notification delegates. Create the persistent store from the Apple source set (iosMain):
val store = PersistentDingCaptureStore.get(
storagePath = dingStoragePath,
)
val ding = DingAppleCapture(store)
ding.updateFcmToken(latestFcmToken)
ding.captureAppleUserInfo(
userInfo = userInfo,
transport = PushTransport.FCM_APNS,
capturePoint = CapturePoint.FOREGROUND,
)FCM and APNs tokens are stored separately, and every snapshot retains the token that was current when it was captured. The host must supply one stable, app-private path ending in .db; the Room-backed store keeps the newest 50 snapshots by default. Reuse the returned store for that path.
InMemoryDingCaptureStore remains available when process-local history is sufficient. The persistent store currently targets callbacks in the main app process. Notification Service Extension/App Group sharing remains a separate follow-up step.
SwiftPM packaging can be verified locally on macOS:
./gradlew :ding-core:packageDingSwiftPM
./gradlew :ding-core:verifyDingLocalSwiftPackageThe first task writes Ding.xcframework.zip and its SwiftPM checksum under ding-core/build/swiftpm/release. The second resolves the local binary target and type-checks Ding's exported API with the Swift compiler. Tagged releases publish that exact archive as a GitHub Release asset and include a root manifest with the matching checksum.
Run the sample module and tap:
Send Local NotificationAdd Demo FCM NotificationsRotate Sample FCM TokenEnable Ding notificationDisable Ding notificationOpen DingThe captured local notification appears under the Local filter. The demo FCM action adds realistic delivery, message, reservation, payment, and activity notifications under FCM.
Notification payloads can contain personal data, authentication material, or internal identifiers. FCM registration tokens identify app instances and are included in raw JSON and exports when captured. Keep Ding on debug builds, review copied or exported content before sharing it, and never attach raw production payloads or tokens to public issues.
Ding is licensed under the Apache License 2.0.