Skip to content

Conversation

@ychescale9
Copy link
Member

@ychescale9 ychescale9 commented May 10, 2025

POC for a Kotlin Compiler Plugin (IR) that transforms the functions annotated with a custom Preview annotation to wrap a user defined lambda call expression around the body.

This is meant to simplify writing Compose Previews by eliminating the need to wrap a Theme Composable around every preview, so instead of:

@Preview
@Composable
fun PreviewFoo() {
    KStreamlinedTheme {
        Surface {
            Foo()
        }
    }
}

We can write this:

@PreviewKStreamlined
@Composable
fun PreviewFoo() {
    Foo()
}

Where @PreviewKStreamlined is a custom Preview annotation we define:

@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION)
@Preview
public annotation class PreviewKStreamlined

We also defines the wrapping function to be used for transforming the function body:

@Composable
public fun KSThemeWithSurface(
    content: @Composable () -> Unit
) {
    KSTheme {
        Surface {
            content()
        }
    }
}

We then specify the custom annotation and the wrapping function to use in the plugin extension:

plugins {
    id("io.github.reactivecircus.cocoon")
    ...
}

cocoon {
    annotation.set("io.github.reactivecircus.kstreamlined.android.foundation.designsystem.preview.PreviewKStreamlined")
    wrappingFunction.set("io.github.reactivecircus.kstreamlined.android.foundation.designsystem.preview.KSThemeWithSurface")
}

During Kotlin compilation the plugin will modify the function body to produce the IR equivalent to:

@PreviewKStreamlined
@Composable
fun PreviewFoo() {
    KSThemeWithSurface {
        Foo()
    }
}

Without the plugin

no-ir-transformed

With the plugin

ir-transformed

Limitations

  1. The plugin has to be applied before the Compose compiler plugin, as it doesn't know how to manipulate functions already transformed by Compose IR.
  2. (⚠️ Deal breaker) Compose Preview ignores plugin after edits - Compose Preview rendering in AS seems to bypass Gradle and uses the Preview sync to compile modified code to generate .class files, and ignores the ones built by Gradle. So user has to press the "Build & Refresh" button everytime after a change.

@ychescale9 ychescale9 changed the title POC - Compiler Plugin (IR) for wrapping Preview function body with Theme wrapper POC - Compiler Plugin (IR) to wrap Theme Composable around Preview function body May 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants