airbnb / Showkase

🔦 Showkase is an annotation-processor based Android library that helps you organize, discover, search and visualize Jetpack Compose UI elements
https://medium.com/airbnb-engineering/introducing-showkase-a-library-to-organize-discover-and-visualize-your-jetpack-compose-elements-d5c34ef01095
Apache License 2.0
2.11k stars 107 forks source link

Impossible to use `@ShowkaseColor` or `@ShowkaseTypography` on objects' fields that have a constructor with no parameters #375

Open francescocervone opened 7 months ago

francescocervone commented 7 months ago

I see from the validator that the enclosing class must have exactly one constructor with no parameters here.

Couldn't this be relaxed to have at least one constructor with no parameters?

I mean, could that function be converted to something like this?

fun validateEnclosingClass(enclosingClass: XTypeElement?) {
  if (enclosingClass == null) return

  val hasEmptyConstructor = enclosingClass.getConstructors().any { constructor -> constructor.parameters.isEmpty() }
  if (!hasEmptyConstructor) {
    throw ShowkaseProcessorException(
      "Only classes that have at least one constructor with no parameters can " +
        "hold a @Composable function that's annotated with the " +
        "@${ShowkaseComposable::class.java.simpleName}/@Preview annotation",
      enclosingClass,
    )
  }
}
vinaygaba commented 4 months ago

@francescocervone Yeah I can see this being useful. I worry that it might end up impacting the preview itself but maybe that's acceptable. Can you talk a bit more about your use case. What does the enclosing class look like in your case?

francescocervone commented 4 months ago

Yes, we have a class for the Typography that depends on the colors, but it might have a default value which is overridden when the theme is applied. Basically we have the colors:

interface Colors {
  val content: Color
  val background: Color
}
class LightColors: Colors {
  @ShowkaseColor
  override val content: Color = // something
  @ShowkaseColor
  override val background: Color = // something
}
class DarkColors: Colors {
  @ShowkaseColor
  override val content: Color = // something
  @ShowkaseColor
  override val background: Color = // something
}

Then we have the typography:

interface Typography {
  val title: TextStyle
  val body: TextStyle
}
class DefaultTypography(private val colors: Colors = LightColors): Typography {
  // we'd like to have @ShowkaseTypography here but we can't
  override val title: TextStyle = // something
  // we'd like to have @ShowkaseTypography here but we can't
  override val body: TextStyle = // something
}

Technically, Showkase would be able to instantiate DefaultTypography because it has at least one empty constructor.