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.json and AndroidManifest.xml each get a tab to preview, copy or download the file. The AndroidManifest.xml tab 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 by appLinkFiles, 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:runDevToolsServer

Open 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:runDevToolsServerHeadless

To 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 windows

Each 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 Simulator

iOS 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

  • createToolingModule builds the Reaktiv module that hosts the tooling services

  • DevToolsService is the service that connects your app to the server

  • NavigationLinks (from reaktiv-navigation-tooling) publishes the navigation map and opens links

  • DevToolsConfig — configuration (server URL, role, capture flags)

  • IntrospectionConfig sets client identity and what gets captured

  • SessionCapture — records actions and logic events for export or crash reports

  • CrashHandler — installs a crash handler that saves the session to disk

  • ClientRole — 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.json and AndroidManifest.xml each get a tab to preview, copy or download the file. The AndroidManifest.xml tab 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 by appLinkFiles, 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:runDevToolsServer

Open 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:runDevToolsServerHeadless

To 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 windows

Each 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 Simulator

iOS 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

  • createToolingModule builds the Reaktiv module that hosts the tooling services

  • DevToolsService is the service that connects your app to the server

  • NavigationLinks (from reaktiv-navigation-tooling) publishes the navigation map and opens links

  • DevToolsConfig — configuration (server URL, role, capture flags)

  • IntrospectionConfig sets client identity and what gets captured

  • SessionCapture — records actions and logic events for export or crash reports

  • CrashHandler — installs a crash handler that saves the session to disk

  • ClientRole — 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.json and AndroidManifest.xml each get a tab to preview, copy or download the file. The AndroidManifest.xml tab 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 by appLinkFiles, 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:runDevToolsServer

Open 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:runDevToolsServerHeadless

To 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 windows

Each 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 Simulator

iOS 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

  • createToolingModule builds the Reaktiv module that hosts the tooling services

  • DevToolsService is the service that connects your app to the server

  • NavigationLinks (from reaktiv-navigation-tooling) publishes the navigation map and opens links

  • DevToolsConfig — configuration (server URL, role, capture flags)

  • IntrospectionConfig sets client identity and what gets captured

  • SessionCapture — records actions and logic events for export or crash reports

  • CrashHandler — installs a crash handler that saves the session to disk

  • ClientRole — PUBLISHER, LISTENER, ORCHESTRATOR

Packages

Link copied to clipboard
common
Link copied to clipboard
common
common
common
Link copied to clipboard
desktop
Link copied to clipboard
common
Link copied to clipboard
common