reaktiv-devtools
Real-time debugging and state inspection tools for Reaktiv applications. DevTools provides a WebSocket-based bridge between your app and a browser-based UI for watching state changes, replaying action streams, and importing crash sessions.
Architecture
Your App --WebSocket--> DevTools Server (native binary)
|
WebSocket v
DevTools UI (WASM, runs in browser)Setup
1. Add the dependency
// build.gradle.kts
dependencies {
implementation("io.github.syrou:reaktiv-devtools:<version>")
}2. Add the tooling module to your store
val store = createStore {
module(CounterModule)
module(navigationModule)
module(
createToolingModule(
config = IntrospectionConfig(
clientName = "My App",
platform = "Android ${Build.VERSION.RELEASE}"
),
platformContext = PlatformContext(applicationContext)
) {
install(
DevToolsService(
DevToolsConfig(
serverUrl = "ws://10.0.2.2:8080/ws",
defaultRole = ClientRole.PUBLISHER
)
)
)
}
)
}PlatformContext() takes no argument outside Android. Crash capture, the stall watchdog and logic tracing are on by default and can be turned off through IntrospectionConfig.
3. Optional: capture network traffic
Install the Ktor plugin on the client your app uses, and requests appear on the same timeline as actions and logic calls:
val httpClient = HttpClient(OkHttp) {
install(ReaktivNetworkInspection)
}See the reaktiv-network-ktor module documentation for configuration, redaction and replay.
4. Optional: the navigation map
Add reaktiv-navigation-tooling next to the DevTools dependency and install NavigationLinks beside DevToolsService:
createToolingModule(config, platformContext) {
install(DevToolsService(DevToolsConfig(serverUrl = "ws://10.0.2.2:8080/ws")))
install(NavigationLinks())
}The Nav tab then opens on a map of every route the app can navigate to:
Graphs are cards, their routes are rows, and guarded zones are drawn as dashed bands.
The current screen and the back stack are marked as the app moves.
The wheel or W and S zoom, dragging or the arrow keys pan, and F fits the whole map. Zoomed out it becomes an overview of graph names.
Double click a graph to fold it, and search to highlight routes by path or screen name.
Click a route to see its screen, params, guards, aliases and web path, copy them, and open it on the device. Opening runs the same plan as a universal link and shows the outcome.
Export JSON downloads the map for sharing, in the format
NavigationModule.linkMap()produces.App links asks for the host, the iOS app IDs, the Android package and its signing fingerprints, and optionally a custom deep link scheme and host. Its Routes tab lets you tick the routes that should open the app (Select all and Select none help when only a few should), and
apple-app-site-association,assetlinks.jsonandAndroidManifest.xmleach get a tab to preview, copy or download the file. TheAndroidManifest.xmltab holds the verified App Links intent filter and, when a scheme is given, the custom scheme deep link filter. Generate and download saves every generated file at once, and ticking routes only refreshes the preview. The files are generated on the device byappLinkFiles, the same function an app can call in a test.
Opening links on the device can be turned off with NavigationLinks(allowOpening = false), and every remote request with DevToolsConfig(allowRemoteRequests = false).
Running the Server and UI
One command builds the WASM UI and serves it together with the WebSocket endpoint on port 8080:
./gradlew :reaktiv-devtools:runDevToolsServerOpen http://localhost:8080. Add -Pport=8081 to use a different port, and point the client's serverUrl at the same one. To skip the WASM build when you only need the transport:
./gradlew :reaktiv-devtools:runDevToolsServerHeadlessTo produce standalone native binaries instead, for example to run the server on another machine:
./gradlew :reaktiv-devtools:buildDevToolsServerFast # current platform
./gradlew :reaktiv-devtools:buildDevToolsServer # linux, macOS and windowsEach binary takes the UI directory as an optional argument, and serves only the WebSocket endpoint without it.
WebSocket URL by Target
serverUrl = "ws://10.0.2.2:8080/ws" // Android Emulator -> host machine
serverUrl = "ws://192.168.1.100:8080/ws" // Real device on same WiFi
serverUrl = "ws://localhost:8080/ws" // iOS SimulatoriOS Info.plist requirements
The DevTools transport is cleartext ws://, which App Transport Security blocks by default. A debug-only Info.plist must opt in, otherwise the connection fails with no obvious cause:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>NSAllowsLocalNetworking covers localhost and private-range addresses, so it is enough for both the simulator and a device on the same WiFi. Connecting to a LAN address from a physical device additionally triggers the iOS local network permission prompt, which needs a purpose string:
<key>NSLocalNetworkUsageDescription</key>
<string>Connects to the Reaktiv DevTools server during development.</string>Keep both keys out of release builds. The per-configuration wiring in docs/tooling-attachment-ios.md keeps the dependency itself debug-only.
Runtime Connection Control
Connect, disconnect, or reconnect without rebuilding:
store.dispatch(DevToolsAction.Connect("ws://192.168.1.100:8080/ws"))
store.dispatch(DevToolsAction.Disconnect)
store.dispatch(DevToolsAction.Reconnect)Ghost Sessions and Crash Capture
Export a recorded session from the DevTools UI and import it later for offline debugging or post-mortem analysis.
// Install the crash handler so sessions are saved on crash
CrashHandler(platformContext, sessionCapture).install()
// Export a session manually (e.g. from a debug menu)
store.launch {
val json = sessionCapture.exportSession()
// Save or share `json` however is appropriate for your platform
}Crash sessions are saved to the device's Downloads (Android) or Documents (iOS) folder. Import them in the DevTools UI as ghost sessions and use time travel to replay events.
Tracing Integration
Add the tracing Gradle plugin to automatically instrument all ModuleLogic methods. The DevTools UI shows method names, parameters, execution duration, and return values.
// build.gradle.kts
plugins {
id("io.github.syrou.reaktiv.tracing") version "<version>"
}Use @NoTrace to exclude helpers, and @Sensitive / @PII to obfuscate parameter values in the trace output — see reaktiv-tracing-annotations for details.
Key Types
createToolingModulebuilds the Reaktiv module that hosts the tooling servicesDevToolsServiceis the service that connects your app to the serverNavigationLinks(fromreaktiv-navigation-tooling) publishes the navigation map and opens linksDevToolsConfig— configuration (server URL, role, capture flags)IntrospectionConfigsets client identity and what gets capturedSessionCapture— records actions and logic events for export or crash reportsCrashHandler— installs a crash handler that saves the session to diskClientRole—PUBLISHER,LISTENER,ORCHESTRATOR
Real-time debugging and state inspection tools for Reaktiv applications. DevTools provides a WebSocket-based bridge between your app and a browser-based UI for watching state changes, replaying action streams, and importing crash sessions.
Architecture
Your App --WebSocket--> DevTools Server (native binary)
|
WebSocket v
DevTools UI (WASM, runs in browser)Setup
1. Add the dependency
// build.gradle.kts
dependencies {
implementation("io.github.syrou:reaktiv-devtools:<version>")
}2. Add the tooling module to your store
val store = createStore {
module(CounterModule)
module(navigationModule)
module(
createToolingModule(
config = IntrospectionConfig(
clientName = "My App",
platform = "Android ${Build.VERSION.RELEASE}"
),
platformContext = PlatformContext(applicationContext)
) {
install(
DevToolsService(
DevToolsConfig(
serverUrl = "ws://10.0.2.2:8080/ws",
defaultRole = ClientRole.PUBLISHER
)
)
)
}
)
}PlatformContext() takes no argument outside Android. Crash capture, the stall watchdog and logic tracing are on by default and can be turned off through IntrospectionConfig.
3. Optional: capture network traffic
Install the Ktor plugin on the client your app uses, and requests appear on the same timeline as actions and logic calls:
val httpClient = HttpClient(OkHttp) {
install(ReaktivNetworkInspection)
}See the reaktiv-network-ktor module documentation for configuration, redaction and replay.
4. Optional: the navigation map
Add reaktiv-navigation-tooling next to the DevTools dependency and install NavigationLinks beside DevToolsService:
createToolingModule(config, platformContext) {
install(DevToolsService(DevToolsConfig(serverUrl = "ws://10.0.2.2:8080/ws")))
install(NavigationLinks())
}The Nav tab then opens on a map of every route the app can navigate to:
Graphs are cards, their routes are rows, and guarded zones are drawn as dashed bands.
The current screen and the back stack are marked as the app moves.
The wheel or W and S zoom, dragging or the arrow keys pan, and F fits the whole map. Zoomed out it becomes an overview of graph names.
Double click a graph to fold it, and search to highlight routes by path or screen name.
Click a route to see its screen, params, guards, aliases and web path, copy them, and open it on the device. Opening runs the same plan as a universal link and shows the outcome.
Export JSON downloads the map for sharing, in the format
NavigationModule.linkMap()produces.App links asks for the host, the iOS app IDs, the Android package and its signing fingerprints, and optionally a custom deep link scheme and host. Its Routes tab lets you tick the routes that should open the app (Select all and Select none help when only a few should), and
apple-app-site-association,assetlinks.jsonandAndroidManifest.xmleach get a tab to preview, copy or download the file. TheAndroidManifest.xmltab holds the verified App Links intent filter and, when a scheme is given, the custom scheme deep link filter. Generate and download saves every generated file at once, and ticking routes only refreshes the preview. The files are generated on the device byappLinkFiles, the same function an app can call in a test.
Opening links on the device can be turned off with NavigationLinks(allowOpening = false), and every remote request with DevToolsConfig(allowRemoteRequests = false).
Running the Server and UI
One command builds the WASM UI and serves it together with the WebSocket endpoint on port 8080:
./gradlew :reaktiv-devtools:runDevToolsServerOpen http://localhost:8080. Add -Pport=8081 to use a different port, and point the client's serverUrl at the same one. To skip the WASM build when you only need the transport:
./gradlew :reaktiv-devtools:runDevToolsServerHeadlessTo produce standalone native binaries instead, for example to run the server on another machine:
./gradlew :reaktiv-devtools:buildDevToolsServerFast # current platform
./gradlew :reaktiv-devtools:buildDevToolsServer # linux, macOS and windowsEach binary takes the UI directory as an optional argument, and serves only the WebSocket endpoint without it.
WebSocket URL by Target
serverUrl = "ws://10.0.2.2:8080/ws" // Android Emulator -> host machine
serverUrl = "ws://192.168.1.100:8080/ws" // Real device on same WiFi
serverUrl = "ws://localhost:8080/ws" // iOS SimulatoriOS Info.plist requirements
The DevTools transport is cleartext ws://, which App Transport Security blocks by default. A debug-only Info.plist must opt in, otherwise the connection fails with no obvious cause:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>NSAllowsLocalNetworking covers localhost and private-range addresses, so it is enough for both the simulator and a device on the same WiFi. Connecting to a LAN address from a physical device additionally triggers the iOS local network permission prompt, which needs a purpose string:
<key>NSLocalNetworkUsageDescription</key>
<string>Connects to the Reaktiv DevTools server during development.</string>Keep both keys out of release builds. The per-configuration wiring in docs/tooling-attachment-ios.md keeps the dependency itself debug-only.
Runtime Connection Control
Connect, disconnect, or reconnect without rebuilding:
store.dispatch(DevToolsAction.Connect("ws://192.168.1.100:8080/ws"))
store.dispatch(DevToolsAction.Disconnect)
store.dispatch(DevToolsAction.Reconnect)Ghost Sessions and Crash Capture
Export a recorded session from the DevTools UI and import it later for offline debugging or post-mortem analysis.
// Install the crash handler so sessions are saved on crash
CrashHandler(platformContext, sessionCapture).install()
// Export a session manually (e.g. from a debug menu)
store.launch {
val json = sessionCapture.exportSession()
// Save or share `json` however is appropriate for your platform
}Crash sessions are saved to the device's Downloads (Android) or Documents (iOS) folder. Import them in the DevTools UI as ghost sessions and use time travel to replay events.
Tracing Integration
Add the tracing Gradle plugin to automatically instrument all ModuleLogic methods. The DevTools UI shows method names, parameters, execution duration, and return values.
// build.gradle.kts
plugins {
id("io.github.syrou.reaktiv.tracing") version "<version>"
}Use @NoTrace to exclude helpers, and @Sensitive / @PII to obfuscate parameter values in the trace output — see reaktiv-tracing-annotations for details.
Key Types
createToolingModulebuilds the Reaktiv module that hosts the tooling servicesDevToolsServiceis the service that connects your app to the serverNavigationLinks(fromreaktiv-navigation-tooling) publishes the navigation map and opens linksDevToolsConfig— configuration (server URL, role, capture flags)IntrospectionConfigsets client identity and what gets capturedSessionCapture— records actions and logic events for export or crash reportsCrashHandler— installs a crash handler that saves the session to diskClientRole—PUBLISHER,LISTENER,ORCHESTRATOR
Real-time debugging and state inspection tools for Reaktiv applications. DevTools provides a WebSocket-based bridge between your app and a browser-based UI for watching state changes, replaying action streams, and importing crash sessions.
Architecture
Your App --WebSocket--> DevTools Server (native binary)
|
WebSocket v
DevTools UI (WASM, runs in browser)Setup
1. Add the dependency
// build.gradle.kts
dependencies {
implementation("io.github.syrou:reaktiv-devtools:<version>")
}2. Add the tooling module to your store
val store = createStore {
module(CounterModule)
module(navigationModule)
module(
createToolingModule(
config = IntrospectionConfig(
clientName = "My App",
platform = "Android ${Build.VERSION.RELEASE}"
),
platformContext = PlatformContext(applicationContext)
) {
install(
DevToolsService(
DevToolsConfig(
serverUrl = "ws://10.0.2.2:8080/ws",
defaultRole = ClientRole.PUBLISHER
)
)
)
}
)
}PlatformContext() takes no argument outside Android. Crash capture, the stall watchdog and logic tracing are on by default and can be turned off through IntrospectionConfig.
3. Optional: capture network traffic
Install the Ktor plugin on the client your app uses, and requests appear on the same timeline as actions and logic calls:
val httpClient = HttpClient(OkHttp) {
install(ReaktivNetworkInspection)
}See the reaktiv-network-ktor module documentation for configuration, redaction and replay.
4. Optional: the navigation map
Add reaktiv-navigation-tooling next to the DevTools dependency and install NavigationLinks beside DevToolsService:
createToolingModule(config, platformContext) {
install(DevToolsService(DevToolsConfig(serverUrl = "ws://10.0.2.2:8080/ws")))
install(NavigationLinks())
}The Nav tab then opens on a map of every route the app can navigate to:
Graphs are cards, their routes are rows, and guarded zones are drawn as dashed bands.
The current screen and the back stack are marked as the app moves.
The wheel or W and S zoom, dragging or the arrow keys pan, and F fits the whole map. Zoomed out it becomes an overview of graph names.
Double click a graph to fold it, and search to highlight routes by path or screen name.
Click a route to see its screen, params, guards, aliases and web path, copy them, and open it on the device. Opening runs the same plan as a universal link and shows the outcome.
Export JSON downloads the map for sharing, in the format
NavigationModule.linkMap()produces.App links asks for the host, the iOS app IDs, the Android package and its signing fingerprints, and optionally a custom deep link scheme and host. Its Routes tab lets you tick the routes that should open the app (Select all and Select none help when only a few should), and
apple-app-site-association,assetlinks.jsonandAndroidManifest.xmleach get a tab to preview, copy or download the file. TheAndroidManifest.xmltab holds the verified App Links intent filter and, when a scheme is given, the custom scheme deep link filter. Generate and download saves every generated file at once, and ticking routes only refreshes the preview. The files are generated on the device byappLinkFiles, the same function an app can call in a test.
Opening links on the device can be turned off with NavigationLinks(allowOpening = false), and every remote request with DevToolsConfig(allowRemoteRequests = false).
Running the Server and UI
One command builds the WASM UI and serves it together with the WebSocket endpoint on port 8080:
./gradlew :reaktiv-devtools:runDevToolsServerOpen http://localhost:8080. Add -Pport=8081 to use a different port, and point the client's serverUrl at the same one. To skip the WASM build when you only need the transport:
./gradlew :reaktiv-devtools:runDevToolsServerHeadlessTo produce standalone native binaries instead, for example to run the server on another machine:
./gradlew :reaktiv-devtools:buildDevToolsServerFast # current platform
./gradlew :reaktiv-devtools:buildDevToolsServer # linux, macOS and windowsEach binary takes the UI directory as an optional argument, and serves only the WebSocket endpoint without it.
WebSocket URL by Target
serverUrl = "ws://10.0.2.2:8080/ws" // Android Emulator -> host machine
serverUrl = "ws://192.168.1.100:8080/ws" // Real device on same WiFi
serverUrl = "ws://localhost:8080/ws" // iOS SimulatoriOS Info.plist requirements
The DevTools transport is cleartext ws://, which App Transport Security blocks by default. A debug-only Info.plist must opt in, otherwise the connection fails with no obvious cause:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>NSAllowsLocalNetworking covers localhost and private-range addresses, so it is enough for both the simulator and a device on the same WiFi. Connecting to a LAN address from a physical device additionally triggers the iOS local network permission prompt, which needs a purpose string:
<key>NSLocalNetworkUsageDescription</key>
<string>Connects to the Reaktiv DevTools server during development.</string>Keep both keys out of release builds. The per-configuration wiring in docs/tooling-attachment-ios.md keeps the dependency itself debug-only.
Runtime Connection Control
Connect, disconnect, or reconnect without rebuilding:
store.dispatch(DevToolsAction.Connect("ws://192.168.1.100:8080/ws"))
store.dispatch(DevToolsAction.Disconnect)
store.dispatch(DevToolsAction.Reconnect)Ghost Sessions and Crash Capture
Export a recorded session from the DevTools UI and import it later for offline debugging or post-mortem analysis.
// Install the crash handler so sessions are saved on crash
CrashHandler(platformContext, sessionCapture).install()
// Export a session manually (e.g. from a debug menu)
store.launch {
val json = sessionCapture.exportSession()
// Save or share `json` however is appropriate for your platform
}Crash sessions are saved to the device's Downloads (Android) or Documents (iOS) folder. Import them in the DevTools UI as ghost sessions and use time travel to replay events.
Tracing Integration
Add the tracing Gradle plugin to automatically instrument all ModuleLogic methods. The DevTools UI shows method names, parameters, execution duration, and return values.
// build.gradle.kts
plugins {
id("io.github.syrou.reaktiv.tracing") version "<version>"
}Use @NoTrace to exclude helpers, and @Sensitive / @PII to obfuscate parameter values in the trace output — see reaktiv-tracing-annotations for details.
Key Types
createToolingModulebuilds the Reaktiv module that hosts the tooling servicesDevToolsServiceis the service that connects your app to the serverNavigationLinks(fromreaktiv-navigation-tooling) publishes the navigation map and opens linksDevToolsConfig— configuration (server URL, role, capture flags)IntrospectionConfigsets client identity and what gets capturedSessionCapture— records actions and logic events for export or crash reportsCrashHandler— installs a crash handler that saves the session to diskClientRole—PUBLISHER,LISTENER,ORCHESTRATOR