airbnb / Showkase

🔦 Showkase is an annotation-processor based Android library that helps you organize, discover, search and visualize Jetpack Compose UI elements

Home Page:https://medium.com/airbnb-engineering/introducing-showkase-a-library-to-organize-discover-and-visualize-your-jetpack-compose-elements-d5c34ef01095

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

adding a new property into @ShowkaseComposable to generate or not the screenshot with Paparazzi integration

IvanSanchez-uam opened this issue · comments

When we skip a preview function with @ShowkaseComposable(skip = true) this preview will not be included into the componentList : List<ShowkaseBrowserComponent> that is the expected behavior, but there is a case when I make the integration with Paparazzi where I don't want to show the preview in the componentBrowser but I want to generate the screenshot, right now this is not possible because as far as the preview was skipped there will not be metadata for it.

A component that is skipped will not be including in the next list

@ShowkaseRootCodegen(
  numComposablesWithoutPreviewParameter = 15,
  numComposablesWithPreviewParameter = 2,
  numColors = 7,
  numTypography = 13,
)
public class PaparazziSampleRootModuleCodegen : ShowkaseProvider {
  public override fun getShowkaseComponents(): List<ShowkaseBrowserComponent> {

    return mutableListOf<ShowkaseBrowserComponent>(
        // components that are skipped are not included here
        BasicChipPreviewChipsBasicChipDefaultStyle,
        BasicChipYellowPreviewChipsBasicChipYellowBackground,
        BottomLabelRowPreviewRowsBottomLabelRow,
        BottomNavigationAlwaysShowLabelComponentPreviewNavigationBottomNavigationBar,
       // more components
    ).apply {
        addAll(H6TextRowComponentPreviewTextH6TextRow)
    }
  }
}

I made changes in the repo locally and the solution was adding a new property into the @ShowkaseComposable annotation, I named the property generateScreenshot as I show next:

@MustBeDocumented
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
@Repeatable
@Suppress("LongParameterList")
annotation class ShowkaseComposable(
    val name: String = "",
    val group: String = "",
    val styleName: String = "",
    val widthDp: Int = -1,
    val heightDp: Int = -1,
    val skip: Boolean = false,
    val defaultStyle: Boolean = false,
    val tags: Array<String> = [],
    val extraMetadata: Array<String> = [],
    val generateScreenshot: Boolean = false,
)

and made changes where needed to use this property in the code. The usage of this property looks like this

@ShowkaseComposable(
    name = "Basic Chip",
    group = "Chips",
    defaultStyle = true,
    generateScreenshot = true,
    skip = true
)
@Composable
fun BasicChipPreview() {
    BasicChip(text = "Chip Component")
}

The generated file for this preview now includes the generateScreenshot property

public val BasicChipPreviewChipsBasicChipDefaultStyle: ShowkaseBrowserComponent =
    ShowkaseBrowserComponent(
        group = "Chips",
        componentName = "Basic Chip",
        functionName = "BasicChipPreview",
        componentKDoc = "",
        componentKey =
            """com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_BasicChipPreview_null_Chips_BasicChip_0_DefaultStyle""",
        isDefaultStyle = true,
        generateScreenshot = true,
        styleName = "Default Style",
        component = @Composable { BasicChipPreview() }
    )

This property in general helps us to generate or not the screenshot with Paparazzi integration.

I have another need, as far as we can not handle the name of the screenshot too much with Paparazzi and It could be something short like the function name of the preview we can write the elementName into the metadata witch is already defined in Showkasemetadata.Component.

@Suppress("LongParameterList")
internal sealed class ShowkaseMetadata {
    abstract val element: XElement
    abstract val packageName: String
    abstract val packageSimpleName: String
    abstract val elementName: String
    abstract val showkaseName: String
    abstract val showkaseGroup: String
    abstract val showkaseKDoc: String
    abstract val enclosingClassName: ClassName?
    abstract val insideWrapperClass: Boolean
    abstract val insideObject: Boolean

    /** A fully qualified prefix for use when de-duplicating components. **/
    val fqPrefix: String
        get() = enclosingClassName?.let {"${it}_$elementName" } ?: "${packageName}_$elementName"

    data class Component(
        override val element: XElement,
        override val packageName: String,
        override val packageSimpleName: String,
        **override val elementName: String**,
        override val showkaseName: String,
        override val showkaseGroup: String,
        override val showkaseKDoc: String,
        override val enclosingClassName: ClassName? = null,
        override val insideWrapperClass: Boolean = false,
        override val insideObject: Boolean = false,
        val componentIndex: Int? = null,
        val showkaseWidthDp: Int? = null,
        val showkaseHeightDp: Int? = null,
        val previewParameterProviderType: TypeName? = null,
        val previewParameterName: String? = null,
        val showkaseStyleName: String? = null,
        val isDefaultStyle: Boolean = false,
        val tags: List<String> = emptyList(),
        val extraMetadata: List<String> = emptyList(),
        val showkaseGenerateScreenshot: Boolean = false
    ) : ShowkaseMetadata()

and write the property into the CodeBlock.Builder.addShowkaseBrowserComponent() method of the class WriterUtils.kt like this

internal fun CodeBlock.Builder.addShowkaseBrowserComponent(
    showkaseMetadata: ShowkaseMetadata.Component,
    isPreviewParameter: Boolean = false
) {
    val componentName = if (showkaseMetadata.componentIndex != null) {
        "_${showkaseMetadata.showkaseName}_${showkaseMetadata.componentIndex}"
    } else {
        "_${showkaseMetadata.showkaseName}"
    }
    var componentKey = (showkaseMetadata.fqPrefix +
            "_${showkaseMetadata.enclosingClassName}" +
            "_${showkaseMetadata.showkaseGroup}" +
            componentName +
            "_${showkaseMetadata.showkaseStyleName}").replace(
        SPACE_REGEX,
        ""
    )
    if (isPreviewParameter) {
        componentKey += "_\$index"
    }
    add(
        "%T(\n",
        ShowkaseBrowserWriter.SHOWKASE_BROWSER_COMPONENT_CLASS_NAME
    )
    doubleIndent()
    add(
        "group = %S,\ncomponentName = %S,\n**elementName** = %S,\ncomponentKDoc = %S,\ncomponentKey = %P,",
        showkaseMetadata.showkaseGroup,
        showkaseMetadata.showkaseName,
        **showkaseMetadata.elementName**,
        showkaseMetadata.showkaseKDoc,
        componentKey,
    )
    // more content
}

and the result of this will look like this in the generated file

public val BasicChipPreviewChipsBasicChipDefaultStyle: ShowkaseBrowserComponent =
    ShowkaseBrowserComponent(
        group = "Chips",
        componentName = "Basic Chip",
        **elementName = "BasicChipPreview"**,
        componentKDoc = "",
        componentKey =
            """com.airbnb.android.showkase.screenshot.testing.paparazzi.sample_BasicChipPreview_null_Chips_BasicChip_0_DefaultStyle""",
        isDefaultStyle = true,
        **generateScreenshot = true**,
        styleName = "Default Style",
        component = @Composable { BasicChipPreview() }
    )

I have the complete implementation, let me know if you want to see it and I can share it. Hope this make sense to you as well.

@IvanSanchez-uam Could you open a PR up so that I can see what this looks like. Early thoughts - I'm not a fan of adding more parameters in the annotation and instead want to explore other ways to handle this. Open to ideas