• Stars
    star
    256
  • Rank 153,691 (Top 4 %)
  • Language
    Kotlin
  • License
    Apache License 2.0
  • Created over 1 year ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Make JVM Android integration test visible πŸ€–πŸ“Έ

Roborazzi

Make JVM Android Integration Test Visible

Roborazzi now supports Robolectric Native Graphics (RNG) and enables screenshot testing.πŸ“£

Why Choose Roborazzi?

Why is screenshot testing important?

Screenshot testing is key to validate your app's appearance and functionality. It efficiently detects visual issues and tests the app as users would use it, making it easier to spot problems. It's quicker than writing many assert statements, ensuring your app looks right and behaves correctly.

What are JVM tests and why test with JVM instead of on Android?

JVM tests, also known as local tests, are placed in the test/ directory and are run on a developer's PC or CI environment. On the other hand, device tests, also known as Instrumentation tests, are written in the androidTest/ directory and are run on real devices or emulators. Device testing can result in frequent failures due to the device environment, leading to false negatives. These failures are often hard to reproduce, making them tough to resolve.

Paparazzi and Roborazzi: A Comparison

Paparazzi is a great tool for visualizing displays within the JVM. However, it's incompatible with Robolectric, which also mocks the Android framework.

Roborazzi fills this gap. It integrates with Robolectric, allowing tests to run with Hilt and interact with components. Essentially, Roborazzi enhances Paparazzi's capabilities, providing a more efficient and reliable testing process by capturing screenshots with Robolectric.

Leveraging Roborazzi in Test Architecture: An Example

Try it out

Available on Maven Central.

Add Robolectric

This library is dependent on Robolectric. Please see below to add Robolectric.

https://robolectric.org/getting-started/

To take screenshots, please use Robolectric 4.10 alpha 1 or later and please add @GraphicsMode(GraphicsMode.Mode.NATIVE) to your test class.

@GraphicsMode(GraphicsMode.Mode.NATIVE)

Apply Roborazzi Gradle Plugin

Roborazzi is available on maven central.

This plugin simply creates Gradle tasks record, verify, compare and passes the configuration to the test.

pluginsbuildscript

Define plugin in root build.gradle

plugins {
  ...
  id "io.github.takahirom.roborazzi" version "[version]" apply false
}

Apply plugin in module build.gradle

plugins {
  ...
  id 'io.github.takahirom.roborazzi'
}

root build.gradle

buildscript {
  dependencies {
    ...
    classpath "io.github.takahirom.roborazzi:roborazzi-gradle-plugin:[version]"
  }
}

module build.gradle

apply plugin: "io.github.takahirom.roborazzi"
Use Roborazzi task Use default unit test task Description

./gradlew recordRoborazziDebug

./gradlew testDebugUnitTest after adding roborazzi.test.record=true to your gradle.properties file.

or

./gradlew testDebugUnitTest -Proborazzi.test.record=true

Record a screenshot

./gradlew compareRoborazziDebug

./gradlew testDebugUnitTest after adding roborazzi.test.compare=true to your gradle.properties file.

or

./gradlew testDebugUnitTest -Proborazzi.test.compare=true

Review changes made to an image. This action will compare the current image with the saved one, generating a comparison image labeled as [original]_compare.png. It also produces a JSON file containing the diff information, which can be found under build/test-results/roborazzi.

./gradlew verifyRoborazziDebug

./gradlew testDebugUnitTest after adding roborazzi.test.verify=true to your gradle.properties file.

or

./gradlew testDebugUnitTest -Proborazzi.test.verify=true

Validate changes made to an image. If there is any difference between the current image and the saved one, the test will fail.

./gradlew verifyAndRecordRoborazziDebug

./gradlew testDebugUnitTest after adding roborazzi.test.verify=true and roborazzi.test.record=true to your gradle.properties file.

or

./gradlew testDebugUnitTest -Proborazzi.test.verify=true -Proborazzi.test.record=true

This task will first verify the images and, if differences are detected, it will record a new baseline.

The comparison image, saved as [original]_compare.png, is shown below:

image

This uses JetNew from Compose Samples. You can check the pull request introducing Roborazzi to the compose-samples here.

Add dependencies

Description Dependencies
Core functions testImplementation("io.github.takahirom.roborazzi:roborazzi:[version]")
Jetpack Compose testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:[version]")
JUnit rules testImplementation("io.github.takahirom.roborazzi:roborazzi-junit-rule:[version]")

How to use

Take a screenshot manually

You can take a screenshot by calling captureRoboImage().

app/src/test/java/../ManualTest.kt

import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.GraphicsMode

// All you need to do is use the captureRoboImage function in the test!
import com.github.takahirom.roborazzi.captureRoboImage


// Tips: You can use Robolectric while using AndroidJUnit4
@RunWith(AndroidJUnit4::class)
// Enable Robolectric Native Graphics (RNG) 
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class ManualTest {
  @get:Rule
  val composeTestRule = createAndroidComposeRule<MainActivity>()

  @Test
  fun captureRoboImageSample() {
    // Tips: You can use Robolectric with Espresso API
    // launch
    ActivitySenario.launch(MainActivity::class.java)

    // Capture screen
    onView(ViewMatchers.isRoot())
      // If you don't specify a screenshot file name, Roborazzi will automatically use the method name as the file name for you.
      // The format of the file name will be as follows:
      // build/outputs/roborazzi/com_..._ManualTest_captureRoboImageSample.png
      .captureRoboImage()

    // Capture Jetpack Compose Node
    composeTestRule.onNodeWithTag("MyComposeButton")
      .onParent()
      .captureRoboImage("build/compose.png")
  }
}

Roborazzi supports the following APIs.

CaptureCode
βœ… Jetpack Compose's onNode()
composeTestRule.onNodeWithTag("MyComposeButton")
  .captureRoboImage()
βœ… Espresso's onView()
onView(ViewMatchers.isRoot())
  .captureRoboImage()
onView(withId(R.id.button_first))
  .captureRoboImage()
βœ… View
val view: View = composeTestRule.activity.findViewById<View>(R.id.button_second)
view.captureRoboImage()
βœ… Jetpack Compose lambda
captureRoboImage() {
  Text("Hello Compose!")
}
βœ… Bitmap
val bitmap: Bitmap = createBitmap(100, 100, Bitmap.Config.ARGB_8888)
  .apply {
    applyCanvas {
      drawColor(android.graphics.Color.YELLOW)
    }
  }
bitmap.captureRoboImage()

Device configuration

You can configure the device by using the @Config annotation and RobolectricDeviceQualifiers.

ConfigurationCode
βœ… Predefined device configuration

You can change the device configuration by adding @Config to the class or method.

@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(qualifiers = RobolectricDeviceQualifiers.Pixel5)
class RoborazziTest {
@Test
@Config(qualifiers = RobolectricDeviceQualifiers.Pixel5)
fun test() {
βœ… Night mode
@Config(qualifiers = "+night")
βœ… Locale
@Config(qualifiers = "+ja")
βœ… Screen size
@Config(qualifiers = RobolectricDeviceQualifiers.MediumTablet)

Integrate to your GitHub Actions

It is easy to integrate Roborazzi to your GitHub Actions.

Add a job to store screenshots

name: store screenshots

on:
  push

env:
  GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx6g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"

jobs:
  test:
    runs-on: macos-latest

    steps:
      - uses: actions/checkout@v3
      - uses: actions/[email protected]
        with:
          distribution: 'zulu'
          java-version: 19

      - name: Gradle cache
        uses: gradle/gradle-build-action@v2

      - name: test
        run: |
          # Create screenshots
          ./gradlew app:recordRoborazziDebug --stacktrace

      # Upload screenshots to GitHub Actions Artifacts
      - uses: actions/upload-artifact@v3
        with:
          name: screenshots
          path: app/build/outputs/roborazzi
          retention-days: 30

Add a job to verify screenshots

name: verify test

on:
  push

env:
  GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx6g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"

jobs:
  test:
    runs-on: macos-latest

    steps:
      - uses: actions/checkout@v3
      - uses: actions/[email protected]
        with:
          distribution: 'zulu'
          java-version: 19

      - name: Gradle cache
        uses: gradle/gradle-build-action@v2

      # Download screenshots from main branch
      - uses: dawidd6/action-download-artifact@v2
        with:
          name: screenshots
          path: app/build/outputs/roborazzi
          workflow: test.yaml
          branch: main

      - name: verify test
        id: verify-test
        run: |
          # If there is a difference between the screenshots, the test will fail.
          ./gradlew app:verifyRoborazziDebug --stacktrace

      - uses: actions/upload-artifact@v3
        if: ${{ always() }}
        with:
          name: screenshot-diff
          path: app/build/outputs/roborazzi
          retention-days: 30

      - uses: actions/upload-artifact@v3
        if: ${{ always() }}
        with:
          name: screenshot-diff-reports
          path: app/build/reports
          retention-days: 30

      - uses: actions/upload-artifact@v3
        if: ${{ always() }}
        with:
          name: screenshot-diff-test-results
          path: app/build/test-results
          retention-days: 30

Advanced workflow Sample: Compare Snapshot Results on Pull Requests

For those who are looking for a more advanced example, we have prepared a sample repository that demonstrates how to use Roborazzi to compare snapshot results on GitHub pull requests. This sample showcases the integration of Roborazzi with GitHub Actions workflows, making it easy to visualize and review the differences between snapshots directly in the pull request comments.

Check out the roborazzi-compare-on-github-comment-sample repository to see this powerful feature in action and learn how to implement it in your own projects.

Example of the comment

RoborazziRule (Optional)

RoborazziRule is a JUnit rule for Roborazzi. RoborazziRule is optional. You can use captureRoboImage() without this rule.

RoborazziRule has two features.

  1. Provide context such as RoborazziOptions and outputDirectoryPath etc for captureRoboImage().
  2. Capture screenshots for each test when specifying RoborazziRule.options.captureType.

For example, The following code generates an output file named **custom_outputDirectoryPath**/**custom_outputFileProvider**-com.github.takahirom.roborazzi.sample.RuleTestWithPath.captureRoboImage.png :

@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class RuleTestWithPath {
  @get:Rule
  val roborazziRule = RoborazziRule(
    options = Options(
      outputDirectoryPath = "$DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH/custom_outputDirectoryPath",
      outputFileProvider = { description, outputDirectory, fileExtension ->
        File(
          outputDirectory,
          "custom_outputFileProvider-${description.testClass.name}.${description.methodName}.$fileExtension"
        )
      }
    ),
  )

  @Test
  fun captureRoboImage() {
    launch(MainActivity::class.java)
    // The file will be saved using the rule's outputDirectoryPath and outputFileProvider
    onView(isRoot()).captureRoboImage()
  }
}

Generate gif image

@Test
fun captureRoboGifSample() {
  onView(ViewMatchers.isRoot())
    .captureRoboGif("build/test.gif") {
      // launch
      ActivityScenario.launch(MainActivity::class.java)
      // move to next page
      onView(withId(R.id.button_first))
        .perform(click())
      // back
      pressBack()
      // move to next page
      onView(withId(R.id.button_first))
        .perform(click())
    }
}

Automatically generate gif with test rule

Note
You don't need to use RoborazziRule if you're using captureRoboImage().

With the JUnit test rule, you do not need to name the gif image, and if you prefer, you can output the gif image only if the test fails.

This test will output this file.

build/outputs/roborazzi/com.github.takahirom.roborazzi.sample.RuleTestWithOnlyFail_captureRoboGifSampleFail.gif

@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class RuleTestWithOnlyFail {
  @get:Rule
  val roborazziRule = RoborazziRule(
    captureRoot = onView(isRoot()),
    options = Options(
      onlyFail = true,
      captureType = RoborazziRule.CaptureType.Gif,
    )
  )

  @Test
  fun captureRoboLastImageSampleFail() {
    // launch
    ActivityScenario.launch(MainActivity::class.java)
    // move to next page
    onView(withId(R.id.button_first))
      .perform(click())
    // should fail because the button does not exist
    // Due to failure, the gif image will be saved in the outputs folder.
    onView(withId(R.id.button_first))
      .perform(click())
  }
}

Automatically generate Jetpack Compose gif with test rule

Test target

@Composable
fun SampleComposableFunction() {
  var count by remember { mutableStateOf(0) }
  Column(
    Modifier
      .size(300.dp)
  ) {
    Box(
      Modifier
        .testTag("MyComposeButton")
        .size(50.dp)
        .clickable {
          count++
        }
    )
    (0..count).forEach {
      Box(
        Modifier
          .size(30.dp)
      )
    }
  }
}

Test (Just add RoborazziRule)

@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class ComposeTest {
  @get:Rule
  val composeTestRule = createAndroidComposeRule<ComponentActivity>()

  @get:Rule
  val roborazziRule = RoborazziRule(
    composeRule = composeTestRule,
    captureRoot = composeTestRule.onRoot(),
    options = RoborazziRule.Options(
      RoborazziRule.CaptureType.Gif()
    )
  )

  @Test
  fun composable() {
    composeTestRule.setContent {
      SampleComposableFunction()
    }
    (0 until 3).forEach { _ ->
      composeTestRule
        .onNodeWithTag("MyComposeButton")
        .performClick()
    }
  }
}

com github takahirom roborazzi sample ComposeTest_composable

RoborazziRule options

You can use some RoborazziRule options

/**
 * This rule is a JUnit rule for roborazzi.
 * This rule is optional. You can use [captureRoboImage] without this rule.
 *
 * This rule have two features.
 * 1. Provide context such as `RoborazziOptions` and `outputDirectoryPath` etc for [captureRoboImage].
 * 2. Capture screenshots for each test when specifying RoborazziRule.options.captureType.
 */
class RoborazziRule private constructor(
  private val captureRoot: CaptureRoot,
  private val options: Options = Options()
) : TestWatcher() {
  /**
   * If you add this annotation to the test, the test will be ignored by
   * roborazzi's CaptureType.LastImage, CaptureType.AllImage and CaptureType.Gif.
   */
  annotation class Ignore

  data class Options(
    val captureType: CaptureType = CaptureType.None,
    /**
     * output directory path
     */
    val outputDirectoryPath: String = provideRoborazziContext().outputDirectory,

    val outputFileProvider: FileProvider = provideRoborazziContext().fileProvider
      ?: defaultFileProvider,
    val roborazziOptions: RoborazziOptions = provideRoborazziContext().options,
  )

  sealed interface CaptureType {
    /**
     * Do not generate images. Just provide the image path to [captureRoboImage].
     */
    object None : CaptureType

    /**
     * Generate last images for each test
     */
    data class LastImage(
      /**
       * capture only when the test fail
       */
      val onlyFail: Boolean = false,
    ) : CaptureType

    /**
     * Generate images for Each layout change like TestClass_method_0.png for each test
     */
    data class AllImage(
      /**
       * capture only when the test fail
       */
      val onlyFail: Boolean = false,
    ) : CaptureType

    /**
     * Generate gif images for each test
     */
    data class Gif(
      /**
       * capture only when the test fail
       */
      val onlyFail: Boolean = false,
    ) : CaptureType
  }

Roborazzi options

data class RoborazziOptions(
  val captureType: CaptureType = if (isNativeGraphicsEnabled()) CaptureType.Screenshot() else CaptureType.Dump(),
  val compareOptions: CompareOptions = CompareOptions(),
  val recordOptions: RecordOptions = RecordOptions(),
) {
  sealed interface CaptureType {
    class Screenshot : CaptureType

    data class Dump(
      val takeScreenShot: Boolean = isNativeGraphicsEnabled(),
      val basicSize: Int = 600,
      val depthSlideSize: Int = 30,
      val query: ((RoboComponent) -> Boolean)? = null,
      val explanation: ((RoboComponent) -> String?) = DefaultExplanation,
    ) : CaptureType {
      companion object {
        val DefaultExplanation: ((RoboComponent) -> String) = {
          it.text
        }
        val AccessibilityExplanation: ((RoboComponent) -> String) = {
          it.accessibilityText
        }
      }
    }
  }

  data class CompareOptions(
    val roborazziCompareReporter: RoborazziCompareReporter = RoborazziCompareReporter(),
    val resultValidator: (result: ImageComparator.ComparisonResult) -> Boolean,
  ) {
    constructor(
      roborazziCompareReporter: RoborazziCompareReporter = RoborazziCompareReporter(),
      /**
       * This value determines the threshold of pixel change at which the diff image is output or not.
       * The value should be between 0 and 1
       */
      changeThreshold: Float = 0.01F,
    ) : this(roborazziCompareReporter, ThresholdValidator(changeThreshold))
  }

  interface RoborazziCompareReporter {
    fun report(compareReportCaptureResult: CompareReportCaptureResult)

    companion object {
      operator fun invoke(): RoborazziCompareReporter {
        ...
      }
    }

    class JsonOutputRoborazziCompareReporter : RoborazziCompareReporter {
      ...

      override fun report(compareReportCaptureResult: CompareReportCaptureResult) {
        ...
      }
    }

    class VerifyRoborazziCompareReporter : RoborazziCompareReporter {
      override fun report(compareReportCaptureResult: CompareReportCaptureResult) {
        ...
      }
    }
  }

  data class RecordOptions(
    val resizeScale: Double = roborazziDefaultResizeScale(),
    val applyDeviceCrop: Boolean = false,
    val pixelBitConfig: PixelBitConfig = PixelBitConfig.Argb8888,
  )

  enum class PixelBitConfig {
    Argb8888,
    Rgb565;

    fun toBitmapConfig(): Bitmap.Config {
      ...
    }

    fun toBufferedImageType(): Int {
      ...
    }
  }
}

Dump mode

If you are having trouble debugging your test, try Dump mode as follows.

image

LICENSE

Copyright 2023 takahirom
Copyright 2019 Square, Inc.
Copyright The Android Open Source Project

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.

More Repositories

1

PreLollipopTransition

Simple tool which help you to implement activity and fragment transition for pre-Lollipop devices.
Java
1,294
star
2

android-postfix-plugin

Android postfix plugin for AndroidStudio
Java
344
star
3

webview-in-coordinatorlayout

Java
247
star
4

material-element

An Android app which provides example of implementing material design animation.
Java
242
star
5

DownloadableCalligraphy

This library provides a way to set default (downloadable) fonts using Support Library Font Resource and chrisjenx/Calligraphy methods.
Java
90
star
6

decomposer

Gradle Plugin that allows you to decompile bytecode compiled with Jetpack Compose Compiler Plugin into Java and check it
Kotlin
80
star
7

debug-alter

Alter Android app behavior without rebuild when debugging.
Kotlin
69
star
8

gradle-version-catalog-converter

Convert `implementation 'androidx.core:core-ktx:1.7.0'` into `androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }`
Kotlin
62
star
9

Kotree

A simple tool to display a text tree with Jetpack Compose🌲
Kotlin
56
star
10

WearHttp

This library provides a means to access the content on the Web easily from AndroidWear. Welcome pull request
Java
49
star
11

WearSharedPreferences

This library provide syncing of SharedPreferences between handheld and wearable. Welcome pullrequest!
Java
39
star
12

Hyperion-Simple-Item

Plugin which add simple menus to Hyperion-Android
Java
36
star
13

kotlin-coroutines-class-diagram

36
star
14

NestedListView

Java
20
star
15

jetpack-compose-exoplayer-sample

Lifecycle aware ExoPlayer PlayerView in Jetpack Compose sample
Kotlin
19
star
16

dagger-hilt-multi-module-sample

Kotlin
18
star
17

inside-jetpack-compose-diagram

16
star
18

fire-annotation

Simple tool which help you to implement Firebase Analytics
Groovy
14
star
19

kotlin-multiplatform-affected-module-detector

Kotlin
13
star
20

android-support-library-diff

Shell
11
star
21

DevInfoNotification

This app notify info such as 'Nexus5 XXHDPI 5.1'
Java
10
star
22

arch-lifecycle-rxjava-binder

LifecycleRxJavaBinder class for binding RxJava and Android Architecture Component LifecycleOwner
Java
9
star
23

android-ab-test-builder

Simple tool which help you to implement A/B Test.
Java
8
star
24

rxjava-2-kotlion-coroutines

Sample to migrate RxJava 2's Single to Coroutines
Kotlin
8
star
25

simple-compose-for-learning-inside-compose

Kotlin
7
star
26

hilt-sample-app

Kotlin
7
star
27

roborazzi-compare-on-github-comment-sample

Kotlin
6
star
28

CanvasAnimations

Android Canvas Animations
Java
6
star
29

android-project-template-2022

Kotlin
6
star
30

constraint-layout-samples

Kotlin
5
star
31

HungarianInspectionPlugin

mField <-> field inspection plugin
Java
5
star
32

android-studio-gradle-sync-debugger

Kotlin
5
star
33

jetpack-compose-markdown

This is a sample to display Markdown with Jetpack Compose
Kotlin
5
star
34

dynamic-feature-with-kotlin-mpp

Dynamic feature module with Kotlin Multi Platform
Kotlin
4
star
35

til

Kotlin
4
star
36

ParallaxTutorialSample

Java
3
star
37

google-maven-repository-jar-finder

HTML
3
star
38

jetbrains-academy-algorithms

Kotlin
3
star
39

UltimateBrowserProjectCoordinatorLayoutScrollExperimental

This is just for experimental. Using CoordinatorLayout Behavior for scroll.
Java
3
star
40

android-theme-and-style-graph

Python
3
star
41

hilt-viewmodel-abstract-inject

Kotlin
3
star
42

selenium-kotlin-script

You can use Kotlin Script to automate anything on the web
Kotlin
3
star
43

kdiff

Simple way to identify whats different between 2 instances in Kotlin . inspired by krzysztofzablocki/Difference
Kotlin
2
star
44

draw-android-view-by-jetpack-compose

Kotlin
2
star
45

coroutines-progress-time-latch

Kotlin
2
star
46

system-ui-flags-debugger

Kotlin
2
star
47

Rin

Broadens Compose Multiplatform's versatility by integrating enhanced state management capabilities.
Kotlin
2
star
48

material-motion-hands-on

Java
2
star
49

DrawableAnimations

Java
2
star
50

refactor_androidx

Java
2
star
51

android-commands

Shell
2
star
52

groupie-compose-item

Kotlin
2
star
53

realm-layered

Java
2
star
54

VectorDrawablePerformance

Java
2
star
55

PlaidAnimation

Java
2
star
56

material-planets

Material solar system live wallpaper app using AnimatedVectorDrawableCompat
Java
2
star
57

snackbar-in-content-view

Java
1
star
58

animation-end-listener-after-fragment-view-destory

Kotlin
1
star
59

WearLocationWatchFace

Use WatchFace API and Flicker API and WearSharedPreferences and WearHttp
Java
1
star
60

databinding-in-library-sample

Kotlin
1
star
61

compose-snapshot-sample

Kotlin
1
star
62

androidmvvm

Java
1
star
63

MultiWindowAppLauncher

Java
1
star
64

ZeroBrowser

Java
1
star
65

VisitorWebCrawler

Java
1
star
66

dagger-transitive-playground

Kotlin
1
star
67

dagger2-sample

Java
1
star
68

ToolbarSamples

Java
1
star
69

kotlin-context-recievers-playground

Kotlin
1
star
70

android-mvvm

android mvvm sample
Java
1
star
71

material3-simple-sample

Simple sample project MaterialComponent -> Material3 -> Dynamic color
Kotlin
1
star
72

AndroidDevChallenge

Kotlin
1
star
73

takahirom

1
star
74

DebugApp

Java
1
star
75

paparazzi-with-robolectric

for reporting
Kotlin
1
star
76

AnnotationSamples

HTML
1
star
77

ResearchMVVM

Java
1
star
78

lifecycle-2.2.0-and-kotlinx-coroutines-test-sample

Kotlin
1
star
79

droidkaigi-2018-fonts

Kotlin
1
star
80

simple-rxjava2-project

Java
1
star
81

android-studio-chromium

On progress
Java
1
star
82

android-gradle-plugin-common-logic-sample

Kotlin
1
star
83

PlayQRCode

Java
1
star
84

OSSRH-88523

1
star
85

conference-app-2022-fork-test

The Official Conference App for DroidKaigi 2022
Kotlin
1
star