Jelly for Android

Add a feedback toolbar to your Android debug builds. Long-press any Compose or XML element, write a note, and send your coding agent the exact source-backed feedback.

com.rajandube:jelly:0.2.3

Why it works well with Compose

Compose screens can be tricky to inspect because a simple layout like a Column or Box can swallow the whole touch area.

Jelly checks both the Compose semantics tree and the slot tree, then chooses the most precise hit. So when you tap a button, card, row, or layout, Jelly can capture the element your feedback is actually about.

Mixed Compose and XML screens are supported too.

Set it up

It takes roughly 40 lines across five files. The main idea is simple: use the real SDK in debug and a no-op version everywhere else.

  • Declare the dependency

    Add Jelly to your version catalog, then pull it into the app module with debugImplementation so it never enters release builds.

    # gradle/libs.versions.toml
    [versions]
    jelly = "0.2.3"
    
    [libraries]
    jelly = { group = "com.rajandube", name = "jelly", version.ref = "jelly" }
    // app/build.gradle.kts
    dependencies {
        debugImplementation(libs.jelly)
    }

    Not using a version catalog? Use debugImplementation("com.rajandube:jelly:0.2.3") directly.

  • Wire the no-op source set

    Point every non-debug variant at src/notDebug/java. AGP already folds src/debug/java into debug builds, so debug gets the real installer and every other variant gets the stub.

    // app/build.gradle.kts → android { sourceSets { … } }
    listOf("release", "qa", "dev_test", "release_test").forEach { variant ->
        getByName(variant).java.srcDir("src/notDebug/java")
    }

    List every non-debug build type your project defines. Use kotlin.srcDir(…) if your sources live under kotlin/.

  • Add the no-op stub

    The non-debug fallback — same function name, no Jelly import — so release code compiles without the SDK on the classpath.

    // app/src/notDebug/java/your/app/devtools/QaInstaller.kt
    package your.app.devtools
    
    import android.app.Application
    
    fun installQaTools(application: Application) {
        // No-op in non-debug builds.
    }
  • Add the real installer

    The debug override. The dev.jelly.* import only resolves here — which is exactly why the stub above exists.

    // app/src/debug/java/your/app/devtools/QaInstaller.kt
    package your.app.devtools
    
    import android.app.Application
    import dev.jelly.Jelly
    import dev.jelly.JellyConfig
    
    fun installQaTools(application: Application) {
        Jelly.install(application = application, config = JellyConfig())
    }
  • Call it from Application.onCreate

    One call. The linker resolves the stub or the real installer per variant, so this line compiles everywhere and installs the toolbar only in debug.

    import your.app.devtools.installQaTools
    
    class MyApp : Application() {
        override fun onCreate() {
            super.onCreate()
            installQaTools(this)
        }
    }

Source attribution

Every annotation includes a Source: File.kt:42 line automatically. Jelly walks the call stack and finds the first frame outside Jelly, Compose, and Kotlin internals.

For more precision inside a screen, tag a root area with:

Modifier.jellySource("LoginScreen.kt", 42)

Everything inside inherits that source.

Jelly lives in an APPLICATION_PANEL sub-window and does not need special permissions. Bottom sheets and dialogs can sit above it, so dismiss them first before annotating. For React Native content inside Android, use Jelly for React Native.

What gets shared

Jelly exports the element, hierarchy, source file, position, feedback, intent, severity, and screenshot. The format matches iOS, React Native, and web, so your coding agent receives the same structure everywhere.