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

Overview

Showkase

Showkase Version Compatible with Compose

Showkase is an annotation-processor based Android library that helps you organize, discover, search and visualize Jetpack Compose UI elements. With minimal configuration it generates a UI browser that helps you easily find your components, colors & typography. It also renders your components in common situations like dark mode, right-to-left layouts, and scaled fonts which help in finding issues early.

Why should you use Showkase?

  • When using component based UI toolkits (like React, Compose, Flutter, etc), our codebase often ends up with hundreds of components that are hard to discover, visualize, search and organize.
  • Showkase eliminates the manual work of maintaining a UI preview/browser app that each company is forced to build in order to maintain their design system.
  • Since all the available UI elements are now easily searchable and discoverable, there is better reuse of UI elements in your repo. This makes it useful for maintaining consistency across your app. The biggest problem for enforcing a design system is discoverability and Showkase hopefully solves that problem for your team.
  • It allows you to quickly visualize @Composable components, Color properties and TextStyle (Typography) as you are building them. The goal is to improve the turnaround time in creating production-ready UI elements.
  • Showkase aids in catching common UI issues early with the help of auto-generated permutations of your components.

Features

  • Super simple setup
  • Support for visualizing composables(@ShowkaseComposable), colors(@ShowkaseColor) & typography(@ShowkaseTypography).
  • First class support for @Preview annotation. If you are already using @Preview for previews in Android Studio, using Showkase is even easier as all those components are included in the Showkase browser.
  • Support for top level, class, object & companion object functions and properties to be annotated with the Showkase annotations.
  • 5 Permutations are auto created for each composable (Basic Example, Dark Mode, RTL, Font Scaled, Display Scaled. Look in the gif above for examples)'. More to be added in the future!
  • Support for searching a Showkase UI element by name or group.
  • KDoc support that shows the documentation that you added for a component in the Showkase browser.
  • Multi-module support for showcasing UI elements across multiple modules.
  • Support for constraining a component with a custom height/width using additional parameters in the annotation.
  • Descriptive error messages so that the users of the library can fix any incorrect setup.
  • Incremental annotation processor that makes the code-gen more performant.

Installation

Using Showkase is straightforward and takes just a couple of minutes to get started.

Step 1: Add the dependency to your module's build.gradle file. If you have a multi-module setup, add this dependency to all the modules with UI elements that should be displayed inside the Showkase browser.

implementation "com.airbnb.android:showkase:1.0.0-beta03"
kapt "com.airbnb.android:showkase-processor:1.0.0-beta03"

Step 2: Add the relevant annotations for every UI element that should be a part of the Showkase browser.

For @Composable components, you can either use the @Preview annotation that Compose comes with or use the @ShowkaseComposable annotation:

@Preview(name = "Custom name for component", group = "Custom group name")
@Composable
fun MyComponent() { ... }

// or

@ShowkaseComposable(name = "Name of component", group = "Group Name")
@Composable
fun MyComponent() { ... }

For Color properties, you can add the @ShowkaseColor annotation to the field:

@ShowkaseColor(name = "Primary Color", group = "Material Design")
val primaryColor = Color(0xFF6200EE)

For TextStyle properties that are useful for typography, you can add the @ShowkaseTypography annotation to the field:

@ShowkaseTypography(name = "Custom name for style", group = "Custom group name")
val h1 = TextStyle(
    fontWeight = FontWeight.Light,
    fontSize = 96.sp,
    letterSpacing = (-1.5).sp
)

Step 3: Define an implementation of the ShowkaseRootModule interface in your root module. If your setup involves only a single module, add this implementation in that module. Ensure that this implementation is also annotated with the @ShowkaseRoot annotation.

@ShowkaseRoot
class MyRootModule: ShowkaseRootModule

Step 4: Showkase is now ready for use! Showkase comes with an Activity that you need to start for accessing the UI browser. Typically you would start this activity from the debug menu of your app but you are free to start this from any place you like! A nice helper extension function getBrowserIntent is generated for you so you might have to build the app once before it's available for use. Just start the intent and that's all you need to do for accessing Showkase!

startActivity(Showkase.getBrowserIntent(context))

Documentation

1. @ShowkaseComposable

Used to annotate @Composable functions that should be displayed inside the Showkase browser. If you are using the @Preview annotation with your @Composable function already then you don't need to use this annotation. Showkase has first class support for @Preview.

Here's how you would use it with your @Composable function:

@ShowkaseComposable(name = "Name", group = "Group")
@Composable
fun MyComposable() {
    .......
    .......
}

Name and group are optional. Look at the properties section to understand the behavior when you don't pass any properties.

Note: Make sure that you add this annotation to only those functions that meet the following criteria:

  • Functions that don't have any parameters
  • If it does have a parameter, it has to be annotated with @PreviewParameter that is provided a PreviewParameterProvider implementation.

This is identical to how @Preview works in Compose as well so Showkase just adheres to the same rules.

{ override val values: Sequence get() = sequenceOf( Person("John", 12), Person("Doe", 20) ) override val count: Int get() = super.count } // Since we return 2 objects through our ParameterProvider, Showkase will create 2 previews // for this single composable function. Each preview will have it's own parameter that's passed // to it - Person("John", 12) for the first preview and Person("Doe", 20) for the second one. // This is an effective way to preview your composables against different sets of data. ">
// Consider a simple data class
data class Person(
    val name: String,
    val age: Int
)

// In order to pass a person object as a parameter to our composable function, we will annotate 
// our parameter with `@PreviewParameter` and pass a `PreviewParameterProvider` implementation.
@ShowkaseComposable(name = "Name", group = "Group") or @Preview(name = "Name", group = "Group")
@Composable
fun MyComposable(
    @PreviewParameter(provider = ParameterProvider::class) person: Person
) {
    ...
}

class ParameterProvider : PreviewParameterProvider<Person> {
    override val values: Sequence<Person>
        get() = sequenceOf(
            Person("John", 12),
            Person("Doe", 20)
        )

    override val count: Int
        get() = super.count
}

// Since we return 2 objects through our ParameterProvider, Showkase will create 2 previews 
// for this single composable function. Each preview will have it's own parameter that's passed 
// to it - Person("John", 12) for the first preview and Person("Doe", 20) for the second one. 
// This is an effective way to preview your composables against different sets of data.

Alternatively, you could simply wrap your function inside another function that doesn't accept any parameters -

@Composable
fun MyComposable(name: String) {
    .......
    .......
}

In order to make this function compatible with Showkase, you could further wrap this function inside a method that doesn't accept parameters in the following way:

@ShowkaseComposable(name = "Name", group = "Group")
@Composable
fun MyComposablePreview() {
    MyComposable("Name")
}
@ShowkaseComposable currently supports the following properties:
Property Name Description
name The name that should be used to describe your @Composable function. If you don't pass any value, the name of the composable function is used as the name.
group The grouping key that will be used to group it with other @Composable functions. This is useful for better organization and discoverability of your components. If you don't pass any value for the group, the name of the class that wraps this function is used as the group name. If the function is a top level function, the composable is added to a "Default Group".
widthDp The width that your component will be rendered inside the Showkase browser. Use this to restrict the size of your preview inside the Showkase browser.
heightDp The height that your component will be rendered inside the Showkase browser. Use this to restrict the size of your preview inside the Showkase browser.
skip Setting this to true will skip this composable from rendering in the Showkase browser. A good use case for this would be when you want to have composable with @Preview but want to stop Showkase from picking it up and rendering it in its browser
2. @ShowkaseColor

Used to annotate Color properties that should be presented inside the Showkase browser. Here's how you would use it with your Color fields:

@ShowkaseColor(name = "Name", group = "Group")
val redColor = Color.Red

@ShowkaseColor("Primary", "Light Colors")
val primaryColor = Color(0xFF6200EE)

Name and group are optional. Look at the properties section below to understand the behavior when you don't pass any properties.

@ShowkaseColor currently supports the following properties:
Property Name Description
name The name that should be used to describe your Color fields. If you don't pass any value, the name of the color field is used as the name.
group The grouping key that will be used to group it with other Color fields. This is useful for better organization and discoverability of your colors. If you don't pass any value for the group, the name of the class that wraps this field is used as the group name. If the field is a top level field, the color is added to a "Default Group".
3. @ShowkaseTypography

Used to annotate TextStyle properties that should be presented inside the Showkase browser. Here's how you would use it with your TextStyle fields:

@ShowkaseTypography(name = "Name", group = "Group")
val h1 = TextStyle(
    fontWeight = FontWeight.Light,
    fontSize = 96.sp,
    letterSpacing = (-1.5).sp
)

Name and group are optional. Look at the properties section below to understand the behavior when you don't pass any properties.

@ShowkaseTypography currently supports the following properties:
Property Name Description
name The name that should be used to describe your TextStyle fields. If you don't pass any value, the name of the textStyle field is used as the name.
group The grouping key that will be used to group it with other TextStyle fields. This is useful for better organization and discoverability of your typography. If you don't pass any value for the group, the name of the class that wraps this field is used as the group name. If the field is a top level field, the textStyle is added to a "Default Group".
4. @ShowkaseRoot

Used to annotate the ShowkaseRootModule implementation class. This is needed to let Showkase know more about the module that is going to be the root module for aggregating all the Showkase supported UI elements across all the different modules(if you are using a multi-module project). If you are only using a single module in your project, add it to that module. You are allowed to have only one @ShowkaseRoot per module.

Here's an example of how you would use it:

@ShowkaseRoot
fun MyRootModule: ShowkaseRootModule

Note: The root module is the main module of your app that has a dependency on all other modules in the app. This is relevant because we generate the Showkase related classes in the package of the root module and we need to be able to access the UI elements across all the sub modules. This is only possible from the root module as it typically has a dependency on all the sub-modules.

5. Showkase Object

The Showkase object is the receiver for all the helper methods that this library generates. Currently there are a few extension functions that are generated with the Showkase object as the receiver. In order to get access to these functions, you need to build the app once so that the methods can be generated for your use.

Extension function Description
getBrowserIntent Helper function that return an intent to start the ShowkaseBrowser activity.
getMetadata Helper function that's give's you access to Showkase metadata. This contains data about all the composables, colors and typography in your codebase that's rendered in the Showkase Browser.
// Example Usage

val intent = Showkase.getBrowserIntent(context)
startActivity(intent)

val metadata = Showkase.getMetadata()
val components = metadata.componentList
val colors= metadata.colorList
val typography = metadata.typographyList

Frequently Asked Questions

Is Airbnb using Jetpack Compose in their main app? Since Jetpack Compose is still super early, we haven't started using Compose just yet. However, given our history with declarative UI(we created Epoxy), we are super excited about Compose and are hoping to be able to use it once the API's are more stable.
Why did you create this library if you aren't using Compose in production? One of the biggest barriers to adopting new technology is the lack of tooling that you are otherwise used to having. We currently have an internal tool that works exactly like Showkase but for classic Android. We created Showkase to ensure that we have the tooling available to be able to move to Compose in the future. Moreover, we think that this tool would benefit everyone who's using Compose so we decided to open source it. Hopefully we can learn along with the community and add features that would benefit everyone.
Can I contribute to this library? Pull requests are welcome! We'd love help improving this library. Feel free to browse through open issues to look for things that need work. If you have a feature request or bug, please open a new issue so we can track it.
How do I provide feedback? The issues tab is the best place to do that.
Why can't we have a single annotation like `Showkase` for all the UI elements? Why did you create a different annotation for each UI element(@ShowkaseComposable for composables, @ShowkaseColor for colors & @ShowkaseTypography for text styles)? This was done mostly for future proofing. Even though these annotations have the same properties right now, it's possible that they will diverge as we add more features. Once more people start using this library, we will get a more clear idea about whether that needs to happen or not. If we find that it didn't evolve the way we expected, we will consider consildating these annotations.
I would like to customize my component browser. Can this library still be useful? We provide a nice helper function that gives you access to the metadata of all your UI elements that are configured to work with Showkase. You can use `Showkase.getMetadata()` to get access to it and then use it in whatever way you see fit.

Coming Soon!

Here are some ideas that we are thinking about. We are also not limited to these and would love to learn more about your use cases.

  • Support for passing a @PreviewParameter parameter to @Preview/@ShowkaseComposable components. (See https://github.com/airbnb/Showkase#1-showkasecomposable)
  • Hooks for screenshot testing. Since all your components are a part of the Showkase browser, this would be a good opportunity to make this a part of your CI and detect diffs in components. (The getMetadata method can be useful to accomplish this. More info here - https://github.com/airbnb/Showkase#5-showkase-object)
  • Support for other UI elements that are a part of your design system (like icons, spacing, etc)
  • Generating a web version of the Showkase browser with documentation, search and screenshots.

Contributing

Pull requests are welcome! We'd love help improving this library. Feel free to browse through open issues to look for things that need work. If you have a feature request or bug, please open a new issue so we can track it.

License

Copyright 2021 Airbnb, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • Integration with Paparazzi screenshot testing

    Integration with Paparazzi screenshot testing

    Showkase is actually providing an integration with facebook screenshot testing. Is it possible to integrate Showkase with Paparazzi (screenshot testing without device, link https://github.com/cashapp/paparazzi)?

    opened by marcorighini 20
  • Dark mode demo not working

    Dark mode demo not working

    Captura de pantalla 2021-03-08 a las 9 26 04

    Hi.

    I'm using this code to change (and animate) dark mode from a debug drawer:

    @Composable
    fun RestaurantMenuNumbersTheme(
        darkTheme: Boolean = isSystemInDarkTheme(),
        darkThemeState: MutableState<Boolean> = remember { mutableStateOf(darkTheme) },
        content: @Composable (MutableState<Boolean>) -> Unit,
    ) {
    
        val transition = updateTransition(targetState = darkThemeState.value)
    
        val colors = if (darkThemeState.value) {
            DarkColorPalette
        } else {
            LightColorPalette
        }
    
        val animateColors = Colors(
            primary = transition.animateColor { colors.primary }.value,
            primaryVariant = transition.animateColor { colors.primaryVariant }.value,
            secondary = transition.animateColor { colors.secondary }.value,
            secondaryVariant = transition.animateColor { colors.secondaryVariant }.value,
            background = transition.animateColor { colors.background }.value,
            surface = transition.animateColor { colors.surface }.value,
            error = transition.animateColor { colors.error }.value,
            onPrimary = transition.animateColor { colors.onPrimary }.value,
            onSecondary = transition.animateColor { colors.onSecondary }.value,
            onBackground = transition.animateColor { colors.onBackground }.value,
            onSurface = transition.animateColor { colors.onSurface }.value,
            onError = transition.animateColor { colors.onError }.value,
            isLight = colors.isLight,
        )
    
        MaterialTheme(
            colors = animateColors,
            typography = Typography,
            shapes = Shapes,
            content = { content(darkThemeState) }
        )
    }
    

    So, showkase not shows the dark mode / l right mode correctly

    opened by alorma 18
  • Can't update from beta08 to beta09

    Can't update from beta08 to beta09

    Hi, when I try to update from beta08 to beta09 (or beta10), the build fails with:

    Unresolved reference: getBrowserIntent

    (the extension function which does this is not generated)

    I can't reproduce it on my open-source project, only in my work project.

    I tried updating Kotlin to 1.5.31 and Compose to 1.0.4, but it didn't help. Any ideas what I can try to debug this?

    opened by davidvavra 17
  • doesn't support @Composables with default params

    doesn't support @Composables with default params

    Hopefully this is enough of a stacktrace with beta12:

    error: com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException: Make sure that the @Composable functions that you annotate with the Preview annotation only have a single parameter that is annotated with @PreviewParameter.
      	at com.airbnb.android.showkase.processor.logging.ShowkaseValidator.validateComponentElement$showkase_processor(ShowkaseValidator.kt:59)
    Supported source version 'RELEASE_8' from annotation processor 'org.jetbrains.kotlin.kapt3.base.ProcessorWrapper' less than -source '11'
    
      	at com.airbnb.android.showkase.processor.ShowkaseProcessor.processPreviewAnnotation(ShowkaseProcessor.kt:101)
      	at com.airbnb.android.showkase.processor.ShowkaseProcessor.processComponentAnnotation(ShowkaseProcessor.kt:75)
    com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException: Make sure that the @Composable functions that you annotate with the Preview annotation only have a single parameter that is annotated with @PreviewParameter.
    
      	at com.airbnb.android.showkase.processor.ShowkaseProcessor.process(ShowkaseProcessor.kt:57)
      	at com.airbnb.android.showkase.processor.BaseProcessor.internalProcess(BaseProcessor.kt:96)
      	at com.airbnb.android.showkase.processor.BaseProcessor.process(BaseProcessor.kt:64)
    
    
    opened by kenyee 16
  • Previewing a component that is scrollable

    Previewing a component that is scrollable

    Hi!

    I have a component that uses the verticalScroll modifier. Previewing it in AS works fine, but when opening the group which contains this composable in the Showkase activity, I get an exception saying:

    java.lang.IllegalStateException: Nesting scrollable in the same direction layouts like ScrollableContainer and LazyColumn is not allowed. If you want to add a header before the list of items please take a look on LazyColumn component which has a DSL api which allows to first add a header via item() function and then the list of items via items().
    

    How should I go about resolving this, or isn't this functionality taken into account yet?

    bug enhancement 
    opened by Dambakk 14
  • Can't build the project if app is a root module

    Can't build the project if app is a root module

    Hi, we have a multi-module project. Previously I had one module 'main' with some submodules containing previews and it worked. But we also have some modules which are not dependencies of 'main' module. Only the top-level 'app' module has a dependency to all modules. I tried to move @ShowkaseRoot to the 'app' module.

    Now building project fails with this exception:

    Caused by: java.lang.NullPointerException
    	at com.airbnb.android.showkase.processor.ShowkaseProcessor.init(ShowkaseProcessor.kt:61)
    	at org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor.init(incrementalProcessors.kt:41)
    	at org.jetbrains.kotlin.kapt3.base.ProcessorWrapper.init(annotationProcessing.kt:197)
    

    Any ideas what might help?

    opened by davidvavra 13
  • Handle private @Preview annotations gracefully

    Handle private @Preview annotations gracefully

    When trying to build a project with private Previews, Showkase complains:

    The methods annotated with Preview can't be private as Showkase won't be able to access them otherwise
    

    Does it have to be an error? Not all previews are Showkase-worthy, some of them can be used just in ide. Maybe it could be a warning or there could be a gradle flag for this? Wdyt? This should be relatively easy, I could contribute if you decide that it's a good idea to add it.

    opened by micHar 12
  • NullPointerException build error for 'showkase-processor-testing' module

    NullPointerException build error for 'showkase-processor-testing' module

    Steps taken

    β€’ Fork the project. β€’ Build the project.

    Expected

    β€’ Project build successful.

    Actual

    β€’ Build error thrown NullPointerException, with the following crashlog:

    Build file '/Users/karandhillon/Desktop/Personal/Showkase/showkase-processor-testing/build.gradle' line: 82
    
    A problem occurred evaluating project ':showkase-processor-testing'.
    > java.lang.NullPointerException (no error message)
    
    * Try:
    Run with --info or --debug option to get more log output. Run with --scan to get full insights.
    
    * Exception is:
    org.gradle.api.GradleScriptException: A problem occurred evaluating project ':showkase-processor-testing'.
    	at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:93)
    	at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.lambda$apply$0(DefaultScriptPluginFactory.java:133)
    	at org.gradle.configuration.ProjectScriptTarget.addConfiguration(ProjectScriptTarget.java:77)
    	at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.apply(DefaultScriptPluginFactory.java:136)
    	at org.gradle.configuration.BuildOperationScriptPlugin$1.run(BuildOperationScriptPlugin.java:65)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
    	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
    	at org.gradle.configuration.BuildOperationScriptPlugin.lambda$apply$0(BuildOperationScriptPlugin.java:62)
    	at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:43)
    	at org.gradle.configuration.BuildOperationScriptPlugin.apply(BuildOperationScriptPlugin.java:62)
    	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$0(DefaultProjectStateRegistry.java:248)
    	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:275)
    	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:247)
    	at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:42)
    	at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:26)
    	at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:35)
    	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:100)
    	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$0(DefaultProjectStateRegistry.java:248)
    	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$3(DefaultProjectStateRegistry.java:308)
    	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:178)
    	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:308)
    	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:289)
    	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:247)
    	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:91)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
    	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
    	at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:63)
    	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:710)
    	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:145)
    	at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:36)
    	at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:64)
    	at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:46)
    	at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:57)
    	at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
    	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
    	at org.gradle.configuration.BuildOperationFiringProjectsPreparer.prepareProjects(BuildOperationFiringProjectsPreparer.java:40)
    	at org.gradle.initialization.DefaultGradleLauncher.prepareProjects(DefaultGradleLauncher.java:228)
    	at org.gradle.initialization.DefaultGradleLauncher.doClassicBuildStages(DefaultGradleLauncher.java:165)
    	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:150)
    	at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:126)
    	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:72)
    	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:67)
    	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:178)
    	at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:67)
    	at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:56)
    	at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:60)
    	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    	at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:63)
    	at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
    	at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:67)
    	at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
    	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:49)
    	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:44)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
    	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
    	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
    	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
    	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:44)
    	at org.gradle.launcher.exec.InProcessBuildActionExecuter.lambda$execute$0(InProcessBuildActionExecuter.java:54)
    	at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:87)
    	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:53)
    	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:29)
    	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.lambda$execute$0(BuildTreeScopeLifecycleBuildActionExecuter.java:33)
    	at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:49)
    	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:32)
    	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:27)
    	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:104)
    	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:55)
    	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:64)
    	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:37)
    	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.lambda$execute$0(SessionScopeLifecycleBuildActionExecuter.java:54)
    	at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:67)
    	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:50)
    	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:36)
    	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
    	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
    	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59)
    	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
    	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:55)
    	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:41)
    	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
    	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
    	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
    	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
    	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
    	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
    	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
    	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
    	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
    	at org.gradle.util.Swapper.swap(Swapper.java:38)
    	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
    	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    	at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
    	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
    	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
    	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
    	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
    	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
    	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
    	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
    	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
    Caused by: java.lang.NullPointerException
    	at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection.from(DefaultConfigurableFileCollection.java:190)
    	at org.gradle.api.internal.file.DefaultFileOperations.configurableFiles(DefaultFileOperations.java:126)
    	at org.gradle.groovy.scripts.DefaultScript.files(DefaultScript.java:158)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at org.gradle.internal.metaobject.BeanDynamicObject$MetaClassAdapter.invokeMethod(BeanDynamicObject.java:484)
    	at org.gradle.internal.metaobject.BeanDynamicObject.tryInvokeMethod(BeanDynamicObject.java:196)
    	at org.gradle.groovy.scripts.BasicScript$ScriptDynamicObject.tryInvokeMethod(BasicScript.java:130)
    	at org.gradle.internal.metaobject.ConfigureDelegate.invokeMethod(ConfigureDelegate.java:77)
    	at build_atncjkjkhxk6as55q39rna32o$_run_closure2.doCall(/Users/karandhillon/Desktop/Personal/Showkase/showkase-processor-testing/build.gradle:82)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at org.gradle.util.ClosureBackedAction.execute(ClosureBackedAction.java:71)
    	at org.gradle.util.ConfigureUtil.configureTarget(ConfigureUtil.java:154)
    	at org.gradle.util.ConfigureUtil.configure(ConfigureUtil.java:105)
    	at org.gradle.api.internal.project.DefaultProject.dependencies(DefaultProject.java:1230)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at org.gradle.internal.metaobject.BeanDynamicObject$MetaClassAdapter.invokeMethod(BeanDynamicObject.java:484)
    	at org.gradle.internal.metaobject.BeanDynamicObject.tryInvokeMethod(BeanDynamicObject.java:196)
    	at org.gradle.internal.metaobject.CompositeDynamicObject.tryInvokeMethod(CompositeDynamicObject.java:98)
    	at org.gradle.internal.extensibility.MixInClosurePropertiesAsMethodsDynamicObject.tryInvokeMethod(MixInClosurePropertiesAsMethodsDynamicObject.java:34)
    	at org.gradle.groovy.scripts.BasicScript$ScriptDynamicObject.tryInvokeMethod(BasicScript.java:134)
    	at org.gradle.internal.metaobject.AbstractDynamicObject.invokeMethod(AbstractDynamicObject.java:163)
    	at org.gradle.groovy.scripts.BasicScript.invokeMethod(BasicScript.java:83)
    	at build_atncjkjkhxk6as55q39rna32o.run(/Users/karandhillon/Desktop/Personal/Showkase/showkase-processor-testing/build.gradle:50)
    	at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:91)
    	... 140 more
    

    Android Studio also throws a lint error as shown below: ss

    bug 
    opened by karanDhillon 12
  • showkase browser is not showing component from submodule

    showkase browser is not showing component from submodule

    In multi module project structure, showkase browser is showing components only from the module where I have @ShowkaseRoot it's ignoring all compassable from submodules.

    opened by vivart 11
  • java.lang.NoSuchMethodError: No static method setContent

    java.lang.NoSuchMethodError: No static method setContent

    I'm using 1.0.0-alpha08 version which is the same compose version as the library. Any ideas on how to solve it?

    java.lang.NoSuchMethodError: No static method setContent$default(Landroidx/activity/ComponentActivity;Landroidx/compose/runtime/Recomposer;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Landroidx/compose/runtime/Composition; in class Landroidx/compose/ui/platform/WrapperKt; or its super classes (declaration of 'androidx.compose.ui.platform.WrapperKt' appears in /data/app/com.yektasarioglu.testapp.debug-ZwvsZzo9Jrsy4DLjhICVeg==/base.apk)
            at com.airbnb.android.showkase.ui.ShowkaseBrowserActivity.onCreate(ShowkaseBrowserActivity.kt:26)
            at android.app.Activity.performCreate(Activity.java:8085)
            at android.app.Activity.performCreate(Activity.java:8073)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1320)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3870)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4076)
            at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:91)
            at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:149)
            at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:103)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2473)
            at android.os.Handler.dispatchMessage(Handler.java:110)
            at android.os.Looper.loop(Looper.java:219)
            at android.app.ActivityThread.main(ActivityThread.java:8349)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
    
    version-compatibility 
    opened by yektasarioglu 11
  • Make stacked preview annotations generate showkaseMetadata components for each annotation

    Make stacked preview annotations generate showkaseMetadata components for each annotation

    This is the next step in supporting Multipreview annotations. Referencing #255 as the first step.

    Here I have added functionality to make a ShowkaseMetadata object for each Preview annotation. Eg.

    @Preview(
        name = "small font",
        group = "font scales",
        fontScale = 0.5f
    )
    @Preview(
        name = "large font",
        group = "font scales",
        fontScale = 1.5f
    )
    @Composable
    fun ComposablePreviewFont() {
    
    }
    
    

    Before, this would only make one component in Showkase. Now it should make one for each preview and place them in their respective group. Here they would both be placed in the group font scales

    Furthermore, I would advice to review this by commit as I had to update the tests because of some arrangement due to a check in the processor. I placed the test update in 8371ec2efd81e6317c85029e377dca3ae8cd0aee.

    I'm leaving this as a draft PR because I'm having some issues with getting this to work with KAPT. With KSP, the method roundEnvironment.getElementsAnnotatedWith(PREVIEW_CLASS_NAME) returns all the elements for multi stacked preview. However, with KAPT, it does not seem to work the same way. Is there some other function I should use here? Any suggestions are more than welcome πŸ˜„ Added a slack post for anyone interested πŸ˜„

    opened by oas004 10
  • Implemented Showkase + Paparazzi artifact to automate screenshot testing

    Implemented Showkase + Paparazzi artifact to automate screenshot testing

    This PR implements the showkase-screenshot-testing-paparazzi artifact that enables complete automation of screenshot testing for your codebase using Showkase + Paparazzi.

    Assuming you were already using Showkase and had previews of your @Composable functions (by using either @Preview or @ShowkaseComposable), this is all the extra setup you will need to add screenshot testing to your codebase using Showkase + Paparazzi

    @ShowkaseScreenshot(rootShowkaseClass = YourShowkaseRootModuleClass::class)
    abstract class MyPaparazziShowkaseScreenshotTest: PaparazziShowkaseScreenshotTest {
        companion object: PaparazziShowkaseScreenshotTest.CompanionObject
    }
    

    With just 3 lines of code, all the previews from your codebase will be screenshot tested using Paparazzi with appropriate defaults 🀯πŸ’₯

    You might have a use case where you also want to screenshot test your components in dark mode or in RTL. Doing so is equally trivial. Just implement the appropriate functions to override the default behavior.

    @ShowkaseScreenshot(rootShowkaseClass = YourShowkaseRootModuleClass::class)
    abstract class MyPaparazziShowkaseScreenshotTest: PaparazziShowkaseScreenshotTest {
        companion object: PaparazziShowkaseScreenshotTest.CompanionObject {
            override fun layoutDirections(): List<LayoutDirection> = listOf(LayoutDirection.Ltr, LayoutDirection.Rtl)
    
            override fun uiModes(): List<PaparazziShowkaseUIMode> = listOf(PaparazziShowkaseUIMode.DEFAULT, PaparazziShowkaseUIMode.DARK)
        }
    }
    

    This will now ensure that all the previews are also screenshot tested in RTL and dark mode configurations. This creates a test matrix so all permutations are screenshot tested, just like you'd expect.

    @airbnb/showkase-maintainers

    opened by vinaygaba 0
  • Skip the style selection screen when just one style is defined

    Skip the style selection screen when just one style is defined

    We don't have more than one style and when the user wants to see the permutations, it's needed to go to style selection and click on the default style. It's not intuitive and most users don't find the permutations screen unless told where to look for it. Style selection screen doesn't make sense for just one style and it would be better to go to the permutations directly. I think that having just one style is a most common setup.

    opened by davidvavra 0
  • Configurable permutations for each composable

    Configurable permutations for each composable

    Currently there are 5 permutations (Basic Example, Dark Mode, RTL, Font Scaled, Display Scaled).

    But in our project we care only about Dark Mode and Font Scale x1.5. It's how our screenshot testing is setup. We would like to see the composables in the same state as our screenshot tests. It would help our QA to check what's covered by screenshot tests and discover bugs easier.

    The idea is to have the permutations configurable.

    opened by davidvavra 0
  • Stop publishing separate debug and release artifacts for the

    Stop publishing separate debug and release artifacts for the "showakse" artifact on maven

    Fixes https://github.com/airbnb/Showkase/issues/274

    Showkase was publishing separate debug and release artifacts to maven and this was causing weirdness when consumers had custom build types that weren't debug and release. Moreover, this wasn't intended to be the case in the first place so I'm attempting to fix this for the future releases.

    Screen Shot 2022-12-20 at 3 03 35 PM

    @airbnb/showkase-maintainers

    opened by vinaygaba 1
  • [Request] Provide dark background for rendering Dark mode previews

    [Request] Provide dark background for rendering Dark mode previews

    Apologies if this is already available, I have not seen any callouts for this in the README.

    Basically, the request is to render a black card background in the [Dark Mode] preview generated by Showkase.

    I defined my own Preview annotation which provides both the Light and Dark versions of content.

    @Preview(
        name = "Light theme",
        showBackground = true,
        backgroundColor = 0xFFFFFFFF,
        uiMode = UI_MODE_NIGHT_NO
    )
    @Preview(
        name = "Dark theme",
        showBackground = true,
        backgroundColor = 0xFF000000,
        uiMode = UI_MODE_NIGHT_YES
    )
    internal annotation class PreviewDefaults
    

    Which generates the following:

    image

    It would be great to have a similar functionality provided by Showkase out of the box. As it stands, the previews look like the following - note the content text in [Dark Mode] can't be read because the background is white, and not black.

    image

    As a workaround, I can wrap my preview content in a box that provides a background (with additional padding so we can see the dashed borders), but I do not think I should do this every time.

    @ShowkaseComposable(
        name = PREVIEW_NAME,
        group = GROUP_CARD,
        defaultStyle = true,
    )
    @PreviewDefaults
    @Composable
    internal fun DashedCardPreview() {
        MyTheme {
            Box(
                modifier = Modifier
                    .background(color = MyTheme.colors.background.page.default)
                    .padding(16.dp)
            ) {
                DashedCardSample()
            }
        }
    }
    
    image
    opened by trod-123 2
  • Requirements around calling `Showkase.getBrowserIntent()` aren't well documented

    Requirements around calling `Showkase.getBrowserIntent()` aren't well documented

    This actually tripped me up for a while. Could you please document that you need the class annotated with @ShowkaseRoot to be defined in the same package, not just the same module, as the place you want to call Showkase.getBrowserIntent()? Also, is there any particular reason behind this limitation?

    documentation 
    opened by carterhudson 0
Releases(1.0.0-beta17)
  • 1.0.0-beta17(Dec 17, 2022)

    • Optimized the code generation to be more performant (https://github.com/airbnb/Showkase/pull/284)
      • We hit an issue with Jacoco that pointed out how our final generated aggregator class had a heavy init method that caused a Jacoco task to fail at scale (1000+ previews). In addition, the root module can become a build time bottleneck as the codebase scales. In this release, we optimized the code generation by moving some of the code generation to the respective modules that have the previews as opposed to generating everything in the root module. This also allows us to leverage incremental compilation better. One nice side effect of this refactor is that your preview functions are now allowed to be internal instead of forcing you to be public. Please file issues if you notice any weird behavior after this refactor.
    • Let the IDE recognize generated code when using KSP (#279 )
      • Some folks complained how the generated classes/methods weren't being detected by the IDE. This happens because the source generated by ksp specifically needs to be registered separately. The linked PR does it for the sample app and shows how you'd do that in your own codebase.
    • Fixed bug in getShowkaseMetadataFromPreview where the preview width was being set as the showkase metadata height (#280)

    Note: We discovered a bug in the 1.0.0-beta16 release hence not going to publish the release notes for it. Please use the 1.0.0-beta17 release instead.

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta15(Dec 16, 2022)

    • Ensure that Multi Preview annotations don't throw a compile time error (https://github.com/airbnb/Showkase/pull/255)
    • Fixed indentation error in generated code (https://github.com/airbnb/Showkase/pull/258)
    • Support stacking of @Preview & @ShowakseComposable annotations. This generates the additional previews even in the ShowkaseBrowser, just like you'd expect. One thing to note is that this only works for ksp. More info about the kapt issue here. (https://github.com/airbnb/Showkase/pull/259)
    • Added the ability to skip previews functions that are private. You can use annotations options and pass the skipPrivatePreviews flag to enable this. Here's how you'd configure it in your build.gradle
    // If you are using ksp
    ksp {
        arg("skipPrivatePreviews", "true")
    }
    
    // If you are using kapt
    kapt {
        arguments {
            arg("skipPrivatePreviews", "true")
        }
    }
    
    • If you are using the auto generated screenshot tests from the showkase-screenshot-testing artifact, you would've noticed that there was really limited information available when the test fails. We now share more information so that it's easier to debug the failing test (https://github.com/airbnb/Showkase/pull/276)
    • Added a sample to demonstrate how you can automate screenshot testing of all the previews in your codebase using Showkase + Paparazzi. Using this setup is a strong recommendation (as opposed to even the showkase-screenshot-testing articact and I'm working on a doc that dives deeper on this topic (https://github.com/airbnb/Showkase/pull/252)
    • Fixed issue where having a BackHandler in a preview would break navigation in the ShowkaseBrowser (https://github.com/airbnb/Showkase/pull/267)
    • Miscellaneous documentations related fixes

    Want to once again give a huge shout out to all the contributors. I'm very grateful for all the help and appreciate everything you do to improve this library πŸ™

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta14(Aug 22, 2022)

    • Bumped up the version of Compose Compiler to use the 1.2.0 stable release (https://github.com/airbnb/Showkase/pull/249)
    • Fix long provider name codegen making code that does not compile (https://github.com/airbnb/Showkase/pull/241)
    • Fix textoverflow on top appbar for components with long names (#240)
    • Optimize code formatting in ShowkaseTheme.kt (#243)
    • Add close button for search field with animation (#242)

    I am particularly excited about this release because all these improvements were from the community, particularly from Odin, who is the MVP for this release πŸ₯³

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta13(Jun 10, 2022)

    This release contains the following exiting changes πŸ₯³

    • Showkase is based off Compose version 1.1.1 (https://github.com/airbnb/Showkase/pull/229)
    • Showkase's automated screenshot testing infra just got a sample integration with Shot, a popular screenshot testing library. This also means that there's a new artifact available showkase-screenshot-testing-shot. Refer this PR for more details. Extensive documentation around automated screenshot testing with Showkase coming shortly (https://github.com/airbnb/Showkase/pull/232)
    • Fix for app already in inverse mode issue (https://github.com/airbnb/Showkase/pull/223)
    • Fix for function names with illegal characters in codegen (https://github.com/airbnb/Showkase/pull/234)
    • Added more documentation around how Showkase supports the ability to document component styles and enables linking all components styles in a single view in the browser (https://github.com/airbnb/Showkase/pull/227)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta12(Feb 10, 2022)

    • Fix unable to find showkase root issue (https://github.com/airbnb/Showkase/commit/725882854130fcc67a5f935d9adba8c585f6c149)
    • Fix super small previews issue(https://github.com/airbnb/Showkase/commit/e1ae330d4b94232f03fe8211780624648fa83e22)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta11(Feb 4, 2022)

    Fixed bug that was introduced in beta09 release that stopped showing sub module components (https://github.com/airbnb/Showkase/issues/213)

    Also contains other fixes and contributions

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta10(Jan 11, 2022)

  • 1.0.0-beta09(Jan 3, 2022)

    This release adds KSP support to Showkase. Thanks to @elihart for his fantastic contributions πŸ‘

    Based on some early analysis, we think that this will speed up the build times for Showkase by 70%!!! (https://github.com/airbnb/Showkase/pull/194)

    In order to use Showkase with ksp, you will need to make the following changes to your Showkase setup in the build.gradle file

    +   plugins {
    +       "com.google.devtools.ksp" version "$KSP_VERSION"
    +   }
    
    
    -    kapt "com.airbnb.android:showkase-processor:1.0.0-beta09"
    +    ksp "com.airbnb.android:showkase-processor:1.0.0-beta09"
    

    In addition, you need to pass the following flag to the gradle command that you use to build/run - -PuseKsp=true. Here's an example of what this would look like

    ./gradlew sample:clean sample:build -i -PuseKsp=true
    
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta08(Jan 3, 2022)

    This release includes a simple change to add additional information for screenshot testing

    • Pass style information down for implementing screenshot test (#187)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta07(Oct 27, 2021)

    • Added the ability to specify if a Composable is a style of another Composable function. Created two new properties in the @ShowkaseComposable annotation - styleName & defaultStyle. Here's how you'd use it.
    @ShowkaseComposable(group = "Chips", name = "BasicChip", defaultStyle = true)
    @Composable
    fun ChipPreview() {
        Chip(isError = false)
    }
    
    @ShowkaseComposable(group = "Chips", name = "BasicChip", styleName = "Error")
    @Composable
    fun ChipErrorPreview() {
        Chip(isError = true)
    }
    
    • Improvements to screenshot testing code generation. This feature is still work in progress so I've intentionally not added documentation for this just yet but it should be coming soon!
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta06(Oct 14, 2021)

  • 1.0.0-beta05(Oct 5, 2021)

  • 1.0.0-beta04(Sep 9, 2021)

    • Fixed bug where using a custom theme on the activity caused a crash with MDC adapter (#168)
    • Built against Compose 1.0.1 and Kotlin 1.5.21 (#166)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta03(Aug 6, 2021)

    • Make Showkase compatible with not including kapt on compile classpath (#162)
    • Added support for components with vertical scroll (#163)
    • Added functionality to allow skipping Previews (#164)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta02(Aug 4, 2021)

    • Adds support for the first stable Compose release - 1.0.0 πŸ₯³
    • Get rid of some unnecessary dependencies
    • Bug fix for Showkase crashing for some usecases where kapt is not included on the compile classpath (https://github.com/airbnb/Showkase/pull/160)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta01(Jul 16, 2021)

  • 1.0.0-alpha12(Jun 27, 2021)

  • 1.0.0-alpha11(Jun 6, 2021)

  • 1.0.0-alpha10(Apr 27, 2021)

  • 1.0.0-alpha09(Mar 26, 2021)

    • Based of Compose 1.0.0-beta03
    • Changes the method name to get the ShowkaseBrowser intent from createShowkaseBrowserIntent to getBrowserIntent. It's also not available as a top level function but you need to access it using Showkase.getBrowserIntent. More info here.
    • Addition of a new API that allows you to access all the UI elements that are configured with Showkase. You can access it using Showkase.getMetadata(). Use cases of this might be to create your custom browser, take screenshots of your UI elements(coming soon), etc.
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-alpha08(Mar 26, 2021)

  • 1.0.0-alpha07(Mar 7, 2021)

  • 1.0.0-alpha06(Feb 4, 2021)

  • 1.0.0-alpha05(Jan 4, 2021)

  • 1.0.0-alpha04(Dec 21, 2020)

Notes is a simple and private notes app. Organize your thoughts, discoveries, and ideas and simplify planning important moments in your life with your digital notepad.

Notes Example Download Download the latest version of the Android app from this link. Building Using Android Studio Clone the repo, open it in Android

Dmitry Savin 1 Jan 3, 2022
Explode compose elements on click! Just add explodeOnClick() modifier!

compose-explode Explode compose elements on click! Just add explodeOnClick() modifier! Inspired from ExplosionField Getting started Go to library/expl

Nikhil Chaudhari 33 Jan 8, 2023
🎨 Jetpack Compose canvas library that helps you draw paths, images on canvas with color pickers and palettes

?? Jetpack Compose canvas library that helps you draw paths and images on canvas with color pickers and palettes. Sketchbook also provides useful components and functions that can easily interact with canvas.

Stream 342 Dec 30, 2022
FullMangement - an application that helps you manage your tasks effectively. built with the latest tachs like Compose UI, Jetpack libraries, and MVVM design pattern.

Full Management is an application that helps you manage your tasks effectively. built with the latest tachs like Compose UI, Jetpack libraries and MVVM design pattern.

Amr algnyat 4 Nov 1, 2022
A collection of animations, compositions, UIs using Jetpack Compose. You can say Jetpack Compose cookbook or play-ground if you want!

Why Not Compose! A collection of animations, compositions, UIs using Jetpack Compose. You can say Jetpack Compose cookbook or play-ground if you want!

Md. Mahmudul Hasan Shohag 186 Jan 1, 2023
An Android / Wear OS app built with Jetpack Compose to search for definitions of English words

EnglishDictionary A simple Android / Wear OS app built with Jetpack Compose to s

daniil 4 Sep 21, 2022
Carol 12 Sep 25, 2022
Butterfly - Butterfly helps to build adaptive and responsive UIs for Android with Jetpack WindowManager

?? Butterfly helps to build adaptive and responsive UIs for Android with Jetpack

Stream 200 Dec 13, 2022
Jetpack Compose based project, used to stress-testing compose features / integrations and explore non-trivial functionality

Project containing Jetpack Compose samples For pagination & network images it uses CATAAS. Known issues Navigation-Compose Issue with fast tapping on

Denis Rudenko 59 Dec 14, 2022
A simple authentication application using Jetpack compose to illustrate signin and sign up using Mvvm, Kotlin and jetpack compose

Authentication A simple authentication application using Jetpack compose to illustrate signin and sign up using Mvvm, Kotlin and jetpack compose Scree

Felix Kariuki 5 Dec 29, 2022
Movies App represent a list of movies, list of categories, search about movie and Save movie in Room Database

What is this project? Movies App represent a list of movies, list of categories, search about movie and Save movie in Room Database Main Features Kotl

Ahmed Omara 23 Dec 13, 2022
Compose-navigation - Set of utils to help with integrating Jetpack Compose and Jetpack's Navigation

Jetpack Compose Navigation Set of utils to help with integrating Jetpack Compose

Adam Kobus 5 Apr 5, 2022
Learn Jetpack Compose for Android by Examples. Learn how to use Jetpack Compose for Android App Development. Android’s modern toolkit for building native UI.

Learn Jetpack Compose for Android by Examples. Learn how to use Jetpack Compose for Android App Development. Android’s modern toolkit for building native UI.

MindOrks 382 Jan 5, 2023
⌨️ A tool that gives you a massive head start when building Compose Desktop apps. It saves you from time-consuming setup and configuration

?? create-compose-app A tool that gives you a massive head start when building Compose based apps. It saves you from time-consuming setup and configur

theapache64 466 Dec 21, 2022
⌨️ A tool that gives you a massive head start when building Compose Desktop apps. It saves you from time-consuming setup and configuration

?? create-compose-app A tool that gives you a massive head start when building Compose based apps. It saves you from time-consuming setup and configur

theapache64 468 Dec 30, 2022
πŸš€πŸžπŸ’ͺ Collection of Images, Modifiers, utility functions for Jetpack Compose to expand and enrich displaying, manipulating, scaling, resizing, zooming, and getting cropped ImageBitmap based on selection area

Collection of Images, Modifiers, utility functions for Jetpack Compose to expand and enrich displaying, manipulating, scaling, resizing, zooming, and getting cropped ImageBitmap based on selection area, before/after image to with handle to show partial of both images and more is cooking up

Smart Tool Factory 207 Dec 26, 2022
Add IndustrialTNT like from old IC2 mod which keeps items after explosion and helps to mine resources

IndustrialTNT Add IndustrialTNT like from old IC2 mod which keeps items after ex

null 0 Jan 9, 2022
Jetpack Compose Boids | Flocking Insect 🐜. bird or Fish simulation using Jetpack Compose Desktop πŸš€, using Canvas API 🎨

?? ?? ?? Compose flocking Ants(boids) ?? ?? ?? Jetpack compose Boids | Flocking Insect. bird or Fish simulation using Jetpack Compose Desktop ?? , usi

Chetan Gupta 38 Sep 25, 2022
This is a sample app(For beginners - App #2) built using Jetpack Compose. It demonstrates the concept of State Hoisting in Jetpack Compose.

JetBMICalculator This is a sample app(For beginners - App #2) built using Jetpack Compose. It demonstrates the concept of State Hoisting in Jetpack Co

BHAVNA THACKER 3 Dec 31, 2022