
Desktop app aids in exploring Jetpack Compose internals by visualizing IR and composition structures. Features include tree comparisons, SlotTable inspection, and integration with other apps for detailed analysis.
Decomposer is a desktop app to help developers investigating jetpack compose internals. The app is built with jetpack compose for desktop. This tool provides two core utilities:
As you know, the compose framework uses a kotlin compiler plugin to rewrite your compose code. It might be helpful to understand how the compiler plugin rewrite your compose code. Compose compiler mainly works on the IR stage of kotlin compiler pipeline and there are challenges to view the IR structure in action. First of all, the IR tree is a transient structure in kotlin compiler, it is not serialized to disk after compilation. Secondly, the IR tree is not human readable. So this decomposer tool solves this problem by rendering the IR to a kotlin like format which makes it much easier to read.
The compose framework stores composition data in an internal data structure called SlotTable. It might be helpful to directly view the SlotTable in a human readable tree structure. The decomposer tool also does that.
There are three main components of this tool:
./gradlew :composeApp:run. Now you can run the sample app on an android device that is connected to the PC via usb cable. This makes sure the desktop app can find the device via adb command../gradlew package../gradlew assemble../gradlew publishToMavenLocal.gradle/libs.version.toml:
[versions]
decomposer = "[version]"
[libs]
decomposer-runtime = { group = "io.github.composexy-decomposer", name = "runtime-android", version.ref = "decomposer" }
[plugins]
decomposer = { id = "io.github.composexy-decomposer", version.ref = "decomposer" }
root project setting.gradle.kts:
pluginManagement {
repositories {
...
mavenLocal()
}
}
root project build.gradle.kts:
plugins {
...
alias(libs.plugins.decomposer) apply false
}
app/build.gradle.kts:
plugins {
alias(libs.plugins.decomposer)
}
// Only enable in debug build
kotlin {
compilerOptions {
val isDebug = project.hasProperty("android")
&& android.buildTypes.find { it.name == "debug" } != null
if (isDebug) {
freeCompilerArgs.addAll(
"-P", "plugin:com.decomposer.compiler:enabled=true",
)
} else {
freeCompilerArgs.addAll(
"-P", "plugin:com.decomposer.compiler:enabled=false"
)
}
}
}
dependencies {
implementation(libs.decomposer.runtime)
}
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
// Decomposer runtime uses websocket to communicate with the desktop app
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".MyApplication"
android:networkSecurityConfig="@xml/network_security_config"
</application>
</manifest>
network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="https://raw.githubusercontent.com/composexy/decomposer/master/system" />
</trust-anchors>
</base-config>
// Makes sure clear traffic is permitted on localhost
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>
app/MyApplication.kt
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Only enable on debug build
if (BuildConfig.DEBUG) {
runtimeInit {
packagePrefixes = listOf(this@MyApplication.packageName)
}
}
}
}
Decomposer is a desktop app to help developers investigating jetpack compose internals. The app is built with jetpack compose for desktop. This tool provides two core utilities:
As you know, the compose framework uses a kotlin compiler plugin to rewrite your compose code. It might be helpful to understand how the compiler plugin rewrite your compose code. Compose compiler mainly works on the IR stage of kotlin compiler pipeline and there are challenges to view the IR structure in action. First of all, the IR tree is a transient structure in kotlin compiler, it is not serialized to disk after compilation. Secondly, the IR tree is not human readable. So this decomposer tool solves this problem by rendering the IR to a kotlin like format which makes it much easier to read.
The compose framework stores composition data in an internal data structure called SlotTable. It might be helpful to directly view the SlotTable in a human readable tree structure. The decomposer tool also does that.
There are three main components of this tool:
./gradlew :composeApp:run. Now you can run the sample app on an android device that is connected to the PC via usb cable. This makes sure the desktop app can find the device via adb command../gradlew package../gradlew assemble../gradlew publishToMavenLocal.gradle/libs.version.toml:
[versions]
decomposer = "[version]"
[libs]
decomposer-runtime = { group = "io.github.composexy-decomposer", name = "runtime-android", version.ref = "decomposer" }
[plugins]
decomposer = { id = "io.github.composexy-decomposer", version.ref = "decomposer" }
root project setting.gradle.kts:
pluginManagement {
repositories {
...
mavenLocal()
}
}
root project build.gradle.kts:
plugins {
...
alias(libs.plugins.decomposer) apply false
}
app/build.gradle.kts:
plugins {
alias(libs.plugins.decomposer)
}
// Only enable in debug build
kotlin {
compilerOptions {
val isDebug = project.hasProperty("android")
&& android.buildTypes.find { it.name == "debug" } != null
if (isDebug) {
freeCompilerArgs.addAll(
"-P", "plugin:com.decomposer.compiler:enabled=true",
)
} else {
freeCompilerArgs.addAll(
"-P", "plugin:com.decomposer.compiler:enabled=false"
)
}
}
}
dependencies {
implementation(libs.decomposer.runtime)
}
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
// Decomposer runtime uses websocket to communicate with the desktop app
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".MyApplication"
android:networkSecurityConfig="@xml/network_security_config"
</application>
</manifest>
network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="https://raw.githubusercontent.com/composexy/decomposer/master/system" />
</trust-anchors>
</base-config>
// Makes sure clear traffic is permitted on localhost
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>
app/MyApplication.kt
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Only enable on debug build
if (BuildConfig.DEBUG) {
runtimeInit {
packagePrefixes = listOf(this@MyApplication.packageName)
}
}
}
}