
Notification Inspector is a lightweight, pure Kotlin Multiplatform (KMP) library designed to capture, log, and inspect push notification payloads directly on-device. Supporting Android, iOS, JVM (Desktop), JavaScript, and WebAssembly, it provides developers with an interactive, on-device UI console to monitor and audit Firebase Cloud Messages (FCM) or Apple Push Notifications (APNs) in real-time, greatly accelerating the development and QA cycle.
| Platform | Support Status | Payload Type | Features |
|---|---|---|---|
| Android (API 21+) | ✅ Fully Implemented |
RemoteMessage (FCM) |
Auto-interception, Standalone Activity UI, Room SQLite DB |
| iOS (iOS 12+) | ✅ Fully Implemented |
NSDictionary (APNs) |
Auto-interception, Embeddable SwiftUI Component, Room SQLite DB |
| JVM (Desktop) | 🚧 Work In Progress | Any |
Coming Soon (no-op stub) |
| JavaScript (JS) | 🚧 Work In Progress | Any |
Coming Soon (no-op stub) |
| WebAssembly (Wasm) | 🚧 Work In Progress | Any |
Coming Soon (no-op stub) |
Debugging push notifications is historically a tedious process. Developers typically have to search through verbose IDE system logs (Logcat/Xcode console), set up complex local proxy tools (Charles, Proxyman), or rely on backend logs to verify that payload properties are being delivered correctly.
Moreover, keeping debugging code and log history in production builds poses significant security risks.
Notification Inspector solves this elegantly by providing:
RemoteMessage on Android, NSDictionary on iOS) to automatically extract and format payloads.shared-no-op library variant is provided. By swapping dependencies in production, all database code, notification interceptors, and UI components are completely stripped out, achieving zero overhead and total security.Add the dependency to your shared/common module's build.gradle.kts file:
sourceSets {
commonMain.dependencies {
// Debug configuration uses the full inspector library
implementation("io.github.srjranjan:shared:1.0.9")
}
}To automatically isolate the inspector to development builds and use the safe no-op implementation in production, declare the dependencies conditionally inside your Android application module's build.gradle.kts:
dependencies {
// Standard debug builds contain the inspector UI and Room DB
debugImplementation("io.github.srjranjan:shared:1.0.9")
// Release builds compile against the empty no-op variant
releaseImplementation("io.github.srjranjan:shared-no-op:1.0.9")
}For native iOS apps or when linking the shared framework directly via Swift Package Manager in Xcode:
https://github.com/srjranjan/Notification-Inspector
main branch).Create an instance of NotificationInspector by passing a PlatformContext. On Android, this requires the application/activity context, whereas on other platforms it requires no arguments:
import com.srj.notificationinspector.NotificationInspector
import com.srj.notificationinspector.PlatformContext
// Android Initialization (typically in Application, Activity, or Service)
val platformContext = PlatformContext(androidContext)
val inspector = NotificationInspector(platformContext)
// iOS / JVM / Desktop Initialization
val platformContext = PlatformContext()
val inspector = NotificationInspector(platformContext)To automatically record incoming notifications, capture them inside your FirebaseMessagingService:
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class MyFirebaseMessagingService : FirebaseMessagingService() {
private lateinit var inspector: NotificationInspector
override fun onCreate() {
super.onCreate()
inspector = NotificationInspector(PlatformContext(applicationContext))
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// Captures, parses, and logs the payload
inspector.capture(remoteMessage)
// Handle your message rendering here...
}
}Launch the standalone Inspector console Activity from anywhere in your debug menu or shake detector:
// Launches InspectorActivity which hosts the Compose Multiplatform UI
inspector.launch()If you want to host the logs UI inside your own custom settings or debug Composable, use NotificationInspectorApp directly:
import com.srj.notificationinspector.ui.NotificationInspectorApp
import com.srj.notificationinspector.getNotificationRepository
@Composable
fun DebugConsoleScreen(context: PlatformContext) {
val repository = remember(context) { getNotificationRepository(context) }
NotificationInspectorApp(
repository = repository,
onClose = { /* Handle navigation back */ }
)
}On iOS, push notification payloads are received as NSDictionary. You can intercept them inside your AppDelegate and display the interface in SwiftUI.
Add payload interception to your UNUserNotificationCenterDelegate implementation:
import Shared
import UserNotifications
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
private let inspector = NotificationInspector(context: PlatformContext())
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// Convert APNs userInfo payload to NSDictionary
let userInfo = response.notification.request.content.userInfo as NSDictionary
// Record and parse payload
inspector.capture(message: userInfo)
completionHandler()
}
}Wrap the exported Compose View Controller inside UIViewControllerRepresentable and display it in a sheet or navigation view:
import SwiftUI
import Shared
// Wrap the Compose controller for SwiftUI
struct NotificationInspectorView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
// MainViewController is generated by the shared KMP library
return MainViewControllerKt.MainViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
// Host it in your debug settings
struct DebugSettingsMenu: View {
@State private var isInspectorOpen = false
var body: some View {
VStack {
Button(action: { isInspectorOpen = true }) {
Label("Open Notification Inspector", systemImage: "bell.badge.fill")
.font(.headline)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
.sheet(isPresented: $isInspectorOpen) {
NotificationInspectorView()
.edgesIgnoringSafeArea(.all)
}
}
}This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details.
Notification Inspector is a lightweight, pure Kotlin Multiplatform (KMP) library designed to capture, log, and inspect push notification payloads directly on-device. Supporting Android, iOS, JVM (Desktop), JavaScript, and WebAssembly, it provides developers with an interactive, on-device UI console to monitor and audit Firebase Cloud Messages (FCM) or Apple Push Notifications (APNs) in real-time, greatly accelerating the development and QA cycle.
| Platform | Support Status | Payload Type | Features |
|---|---|---|---|
| Android (API 21+) | ✅ Fully Implemented |
RemoteMessage (FCM) |
Auto-interception, Standalone Activity UI, Room SQLite DB |
| iOS (iOS 12+) | ✅ Fully Implemented |
NSDictionary (APNs) |
Auto-interception, Embeddable SwiftUI Component, Room SQLite DB |
| JVM (Desktop) | 🚧 Work In Progress | Any |
Coming Soon (no-op stub) |
| JavaScript (JS) | 🚧 Work In Progress | Any |
Coming Soon (no-op stub) |
| WebAssembly (Wasm) | 🚧 Work In Progress | Any |
Coming Soon (no-op stub) |
Debugging push notifications is historically a tedious process. Developers typically have to search through verbose IDE system logs (Logcat/Xcode console), set up complex local proxy tools (Charles, Proxyman), or rely on backend logs to verify that payload properties are being delivered correctly.
Moreover, keeping debugging code and log history in production builds poses significant security risks.
Notification Inspector solves this elegantly by providing:
RemoteMessage on Android, NSDictionary on iOS) to automatically extract and format payloads.shared-no-op library variant is provided. By swapping dependencies in production, all database code, notification interceptors, and UI components are completely stripped out, achieving zero overhead and total security.Add the dependency to your shared/common module's build.gradle.kts file:
sourceSets {
commonMain.dependencies {
// Debug configuration uses the full inspector library
implementation("io.github.srjranjan:shared:1.0.9")
}
}To automatically isolate the inspector to development builds and use the safe no-op implementation in production, declare the dependencies conditionally inside your Android application module's build.gradle.kts:
dependencies {
// Standard debug builds contain the inspector UI and Room DB
debugImplementation("io.github.srjranjan:shared:1.0.9")
// Release builds compile against the empty no-op variant
releaseImplementation("io.github.srjranjan:shared-no-op:1.0.9")
}For native iOS apps or when linking the shared framework directly via Swift Package Manager in Xcode:
https://github.com/srjranjan/Notification-Inspector
main branch).Create an instance of NotificationInspector by passing a PlatformContext. On Android, this requires the application/activity context, whereas on other platforms it requires no arguments:
import com.srj.notificationinspector.NotificationInspector
import com.srj.notificationinspector.PlatformContext
// Android Initialization (typically in Application, Activity, or Service)
val platformContext = PlatformContext(androidContext)
val inspector = NotificationInspector(platformContext)
// iOS / JVM / Desktop Initialization
val platformContext = PlatformContext()
val inspector = NotificationInspector(platformContext)To automatically record incoming notifications, capture them inside your FirebaseMessagingService:
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class MyFirebaseMessagingService : FirebaseMessagingService() {
private lateinit var inspector: NotificationInspector
override fun onCreate() {
super.onCreate()
inspector = NotificationInspector(PlatformContext(applicationContext))
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// Captures, parses, and logs the payload
inspector.capture(remoteMessage)
// Handle your message rendering here...
}
}Launch the standalone Inspector console Activity from anywhere in your debug menu or shake detector:
// Launches InspectorActivity which hosts the Compose Multiplatform UI
inspector.launch()If you want to host the logs UI inside your own custom settings or debug Composable, use NotificationInspectorApp directly:
import com.srj.notificationinspector.ui.NotificationInspectorApp
import com.srj.notificationinspector.getNotificationRepository
@Composable
fun DebugConsoleScreen(context: PlatformContext) {
val repository = remember(context) { getNotificationRepository(context) }
NotificationInspectorApp(
repository = repository,
onClose = { /* Handle navigation back */ }
)
}On iOS, push notification payloads are received as NSDictionary. You can intercept them inside your AppDelegate and display the interface in SwiftUI.
Add payload interception to your UNUserNotificationCenterDelegate implementation:
import Shared
import UserNotifications
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
private let inspector = NotificationInspector(context: PlatformContext())
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// Convert APNs userInfo payload to NSDictionary
let userInfo = response.notification.request.content.userInfo as NSDictionary
// Record and parse payload
inspector.capture(message: userInfo)
completionHandler()
}
}Wrap the exported Compose View Controller inside UIViewControllerRepresentable and display it in a sheet or navigation view:
import SwiftUI
import Shared
// Wrap the Compose controller for SwiftUI
struct NotificationInspectorView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
// MainViewController is generated by the shared KMP library
return MainViewControllerKt.MainViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
// Host it in your debug settings
struct DebugSettingsMenu: View {
@State private var isInspectorOpen = false
var body: some View {
VStack {
Button(action: { isInspectorOpen = true }) {
Label("Open Notification Inspector", systemImage: "bell.badge.fill")
.font(.headline)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
.sheet(isPresented: $isInspectorOpen) {
NotificationInspectorView()
.edgesIgnoringSafeArea(.all)
}
}
}This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details.