A sample of how to implement a design system in Jetpack Compose

Overview

[WIP] Sample Design System

This project aims to hold an example of how to implement a design system with Jetpack Compose.

At this moment it only has the bare minimum to showcase how to implement screenshot testing for multi-themes as described in this blog post.

You might also like...
A Jetpack Compose implementation of Owl, a Material Design study
A Jetpack Compose implementation of Owl, a Material Design study

Owl sample This sample is a Jetpack Compose implementation of Owl, a Material De

FullMangement - an application that helps you manage your tasks effectively. built with the latest tachs like Compose UI, Jetpack libraries, and MVVM design pattern.
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.

ComposeLoginScreen - A sleek design of a simple login screen using Jetpack Compose
ComposeLoginScreen - A sleek design of a simple login screen using Jetpack Compose

ComposeLoginScreen A sleek design of a simple login screen using Jetpack Compose

A setting library for Jetpack Compose with Material You design
A setting library for Jetpack Compose with Material You design

ComposeSetting This is a basic Compose setting library that provides a basic Material3 setting components It also provides a persistent state system b

This is a sample app(For beginners - App #1) built using Jetpack Compose
This is a sample app(For beginners - App #1) built using Jetpack Compose

This is a sample app(For beginners - App #1) built using Jetpack Compose. It is a simple - single screen app to demonstrate use of basic Jetpack Compose UI elements like Text, Image and Button & LazyColumn (Vertical Recyclerview). It also demonstrates how compose manages state with a Boolean State.

Android Sample Kotlin+ MVI + Jetpack compose + Coroutines + Retrofit + Hilt  + Room + Navigation component
Android Sample Kotlin+ MVI + Jetpack compose + Coroutines + Retrofit + Hilt + Room + Navigation component

MVIComposeSample Android Sample app to show user latest movies implementing MVI + Clean Architecture using kotlin & Jetpack compose following solid an

Schedule sample to play with Jetpack Compose Desktop
Schedule sample to play with Jetpack Compose Desktop

Schedule sample to play with Jetpack Compose Desktop

A sample project in Kotlin to demonstrate Jetpack Compose, MVVM, Coroutines, Hilt, Room, Coil, Retrofit, Moshi, Leak Canary and Repository pattern

Jetpack-Compose-Boilerplate This repository contains a sample project in Kotlin to demonstrate Jetpack Compose, MVVM, Coroutines, Hilt, Room, Coil, Re

Sample of usage VisualFSM for Android application - Kotlin Coroutines, Jetpack Compose
Sample of usage VisualFSM for Android application - Kotlin Coroutines, Jetpack Compose

Sample of usage VisualFSM for Android application - Kotlin Coroutines, Jetpack Compose ENG | RUS VisualFSM is a Kotlin library that implements an MVI

Comments
  • add support to screenshot tests

    add support to screenshot tests

    What

    This PR adds support to screenshot tests in the :app module. Each test will take screenshot for both the light and dark them variant.

    1. Integrating with Shot

    In order to be able to have screenshot tests we are integrating Shot as our screenshot testing tool.

    Any test can have the ability to take screenshot by:

    • Extending shot.ScreenshotTest
    • Calling compareScreenhot(rule: ComposeTestRule).

    This way we could easily have a screenshot test in this form:

    class TypographyTest {
       
       @get:Rule
       val composeTestRule = createComposeRule()
    
       @Test
       fun paragraph() {
           composeTestRule.setContent {
              Theme {
                  Text(text = "This is a paragraph.", style = Theme.typography.paragraph)
               }
           }
        
           compareScreenshot(rule = composeTestRule)
       }
    }
    

    By doing this we then have a screenshot generated in our module under app/screenshots/com.fabiocarballo.designsystem.TypographyTest.label

    com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_paragraph_light

    2. Improving the API

    At this point, we know how to make a screenshot test, however as we grow the number of tests we would be having a lot of structural duplication:

    1. Extending ScreenshotTest
    2. Declaring the ComposeTestRule
    3. Setting the composable content (and not forgetting to wrap it under our Theme)
    4. Comparing the results of the screenshot.

    We can then extract this structure into a DesignSystemScreenshotTest:

    abstract class DesignSystemScreenshotTest: ScreenshotTest {
    
       @get:Rule
       val composeTestRule = createComposeRule()
    
       fun runScreenshotTest(content: @Composable () -> Unit) {
             composeTestRule.setContent {
                 Theme(content = content)
              }
              
              compareScreenshot(composeTestRule)
       }
    }
    

    You can see that all the structure was then passed into this abstract class that every test class should extend. Another point to note is that all content should be run under the scope of this runScreenshotTest. Let's see how our TypographyTest would look like now:

    class TypographyTest: DesignSystemScreenshotTest() {
        
        @Test
        fun paragraph() = runScreenshotTest {
           Text(text = "This is a paragraph.", style = Theme.typography.paragraph)
        }
    
        @Test
        fun label() = runScreenshotTest {
            Text(text = "This is a label.", style = Theme.typography.label)
        }
    
        @Test
        fun display() = runScreenshotTest {
            Text(text = "This is a display.", style = Theme.typography.display)
        }
    }
    

    The goal here was to make a test being almost as easy as just declare the composable that should be screenshotted.

    By now, our screenshots would be:

    | Label | Paragraph | Display |
    |---|---|---| | com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_label_light | com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_paragraph_light | com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_display_light |

    3. Add support to multi-theme

    At this point, we have now the capability to easily generate our screenshots for our Light Theme. As a next step, we want to use the same codebase and automatically generate screenshot also to Dark Theme.

    For that, we are going to enrich our DesignSystemScreenshotTest with the capability to run parameterized tests. Hence, we are using Test Parameter Injector to build the parameterized behavior.

    What we are going to do first is to declare a private enum with the themes we want to parameterize with:

    private enum class ThemeMode { LIGHT, DARK }
    

    Then we are going to declare it as a test parameter so that each test will run with both LIGHT and DARK. This is done by adding a field annotated with @TestParameter and by adding integrating with the TestParameterInjector test runner.

    @RunWith(TestParameterInjector::class)
    abstract class DesignSystemScreenshotTest : ScreenshotTest {
    
        @get:Rule
        val composeTestRule = createComposeRule()
    
        @TestParameter
        private lateinit var themeMode: ThemeMode
    
        fun runScreenshotTest(
            content: @Composable () -> Unit
        ) {
    
            composeTestRule.setContent {
                Theme(
                    isSystemInDarkMode = themeMode == ThemeMode.DARK,
                    content = content
                )
            }
    
            compareScreenshot(
                rule = composeTestRule,
            )
        }
    
        private enum class ThemeMode { LIGHT, DARK }
    }
    

    However, we still have one small problem: Shot default behavior is to name the screenshot as "ClassName_MethodName". This way, we actually record the screenshots for the test in both modes, but the last run always overrides the first one.

    To fix that, we are going to generate the screenshot name by ourselves with the help of this helper method:

    private fun extractClassAndMethodName(): String {
        val stack = Throwable().stackTrace
    
        stack.forEach { element ->
            try {
                val clazz = Class.forName(element.className)
                val method = clazz.getMethod(element.methodName)
    
                if (method.annotations.any { it.annotationClass == Test::class }) {
                    return "${clazz.canonicalName}_${method.name}"
                }
            } catch (ignored: NoSuchMethodException) {
                // do nothing
            } catch (ignored: ClassNotFoundException) {
                // do nothing
            }
        }
    
        error("Couldn't parse the name")
    }
    

    This method will simply use the StackTrace to figure out what is the test class and method name. We can then use this information together with the ThemeMode that is being used for the test to generate a test name as:

    • TypographyTest_paragraph_light
    • TypographyTest_paragraph_dark

    Below you have the final version of the DesignSystemScreenshotTest:

    @RunWith(TestParameterInjector::class)
    abstract class DesignSystemScreenshotTest : ScreenshotTest {
    
        @get:Rule
        val composeTestRule = createComposeRule()
    
        @TestParameter
        private lateinit var themeMode: ThemeMode
    
        fun runScreenshotTest(
            content: @Composable () -> Unit
        ) {
    
            composeTestRule.setContent {
                Theme(
                    isSystemInDarkMode = themeMode == ThemeMode.DARK,
                    content = content
                )
            }
    
            val name = "${extractClassAndMethodName()}_${themeMode.name.lowercase()}"
    
            compareScreenshot(
                rule = composeTestRule,
                name = name
            )
        }
    
        private enum class ThemeMode { LIGHT, DARK }
    }
    

    And that is it, when you use run your screenshot tests you will generate both Light and Dark mode screenshots in one go. In our example, the generate screenshots are the following:

    | Label | Paragraph | Display |
    |---|---|---| | com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_label_light| com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_paragraph_light | com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_display_light | com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_label_dark | com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_paragraph_dark| com fabiocarballo designsystem TypographyTest_com fabiocarballo designsystem TypographyTest_display_dark|

    opened by fabiocarballo 0
Owner
Fábio Carballo
Fábio Carballo
Andromeda is a open-source design language system implemented in Jetpack Compose.

Andromeda Andromeda is a open-source design language system implemented in Jetpack Compose. Catalog app Table of Contents About the Project Getting St

Adit Lal 140 Dec 25, 2022
Decathlon Design System UI components for Compose applications

Vitamin Compose Decathlon Design System libraries for android applications Website Compose Decathlon Design System is based on Material Design compose

Decathlon 168 Dec 22, 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
A lightweight particle system for Jetpack Compose - Quarks

compose-particle-system Quarks is a lightweight particle system for Jetpack Compose. There are endless possibilities for creating generative art with

Nikhil Chaudhari 41 Dec 28, 2022
Customisable Preview of system UI decoration for Jetpack Compose.

AdvancedPreview Customisable Preview of system UI decoration for Jetpack Compose. Use cases Want the Preview in Android Studio to look more like in re

Mobnetic 28 Dec 5, 2022
Navigation-Compose - A sample to showcase Kotlin, MVVM, Hilt, Coroutines, StateFlow, Jetpack compose

Navigation-Compose A sample to showcase Kotlin, MVVM, Hilt, Coroutines, StateFlo

Mohammadali Rezaei 6 Jul 13, 2022
New style for app design Online Flora Go Go App UI made in Jetpack Compose. 😉 😎

JetComposeLoginUI New style for app design Online Flora Go Go App UI made in Jetpack Compose. ?? ?? (Navigation Components, Dagger-Hilt, Material Comp

Arvind Meshram 95 Dec 22, 2022
New style for app design E-commerce Shop App UI made in Jetpack Compose.😉😎

E-commerceShopAppUI-Android New style for app design E-commerce Shop App UI made in Jetpack Compose. ?? ?? (Navigation Components, Dagger-Hilt, Materi

Arvind Meshram 30 Jan 8, 2023
Recipes Mobile App UI Design in Jetpack Compose

Recipes Mobile App UI Design in Jetpack Compose Watch it on YouTube Beautiful android mobile Recipes App designed using Jetpack Compose. Single screen

Juraj Kusnier 21 Oct 14, 2022
New style for app design and Movies App with Movies API JetMaxMovies made in Jetpack Compose.😉😎

JetMaxMovie New style for app design and Movies App with Movies API JetMaxMovies made in Jetpack Compose. ?? ?? (Navigation Compose,Dagger-Hilt, Mater

Arvind Meshram 6 Jul 6, 2022