pull/1459/merge
Jose Alcérreca 1 month ago committed by GitHub
commit 8aa9482bc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -27,3 +27,5 @@ kotlin.incremental=false
# If you want to treat warnings as errors locally, set this property to true
# in your ~/.gradle/gradle.properties file.
warningsAsErrors=false
android.experimental.androidTest.enableEmulatorControl=true

@ -221,6 +221,7 @@ jobs:
disable-animations: true
disk-size: 6000M
heap-size: 600M
emulator-options: -show-kernel -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
script: ./gradlew connectedDemoDebugAndroidTest --daemon
- name: Run local tests (including Roborazzi) for the combined coverage report (only API 30)
@ -261,3 +262,143 @@ jobs:
compression-level: 1
overwrite: false
path: '**/build/reports/jacoco/'
androidTestScreenshots:
runs-on: ubuntu-latest
timeout-minutes: 55
strategy:
matrix:
include:
- api-level: 27
profile: pixel_5
target: default
# - api-level: 33
# profile: pixel_fold
# target: google_apis
- api-level: 35
profile: pixel_fold
target: google_apis
steps:
- name: Delete unnecessary tools 🔧
uses: jlumbroso/free-disk-space@v1.3.1
with:
android: false # Don't remove Android tools
tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY"
dotnet: true # rm -rf /usr/share/dotnet
haskell: true # rm -rf /opt/ghc...
swap-storage: true # rm -f /mnt/swapfile (4GiB)
docker-images: false # Takes 16s, enable if needed in the future
large-packages: false # includes google-cloud-sdk and it's slow
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
ls /dev/kvm
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v3
# TODO this workflow needs cmdline-tools 13.0 (for pixel_fold). Remove when not necessary.
- name: Setup Android SDK
uses: android-actions/setup-android@v3
# android-emulator-runner uses /latest
- name: Remove old cmdlinetools
run: |
rm -rf ${{ env.ANDROID_HOME}}/cmdline-tools/latest
# mv ${{ env.ANDROID_HOME}}/cmdline-tools/11479570 ${{ env.ANDROID_HOME}}/cmdline-tools/latest
# echo ${{ env.ANDROID_HOME}}/cmdline-tools/latest/bin >> $GITHUB_PATH
# https://github.com/ReactiveCircus/android-emulator-runner/issues/197
- name: Create directory for AVD
run: mkdir -p /home/runner/.android/avd
- name: Collect Workflow Telemetry
uses: catchpoint/workflow-telemetry-action@v2
with:
comment_on_pr: false
- name: Build projects and run instrumented screenshot tests
id: dropshotsverify
continue-on-error: true
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
disable-animations: true
disk-size: 6000M
emulator-build: 12895296 # 35.4.5 - https://developer.android.com/studio/emulator_archive
heap-size: 600M
profile: ${{ matrix.profile }}
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script:
adb logcat > logcat.log & ./gradlew :app:connectedDemoDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation=com.google.samples.apps.nowinandroid.ui.InstrumentedScreenshotTests --daemon
- name: Upload logcat
if: always()
uses: actions/upload-artifact@v4
with:
name: logcat-${{ matrix.profile }}-${{ matrix.api-level }}
path: logcat.log
- name: Prevent pushing new screenshots if this is a fork
id: checkfork_screenshots_instrumented
continue-on-error: false
if: steps.dropshotsverify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Instrumented screenshot tests failed, please create a PR in your fork first." && exit 1
- name: Record new screenshots if verification failed
id: dropshotsrecord
continue-on-error: false
if: steps.dropshotsverify.outcome == 'failure'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
disable-animations: true
disk-size: 6000M
emulator-build: 12895296 # 35.4.5 - https://developer.android.com/studio/emulator_archive
heap-size: 600M
profile: ${{ matrix.profile }}
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script:
./gradlew :app:connectedDemoDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation=com.google.samples.apps.nowinandroid.ui.InstrumentedScreenshotTests -Pdropshots.record --daemon --stacktrace
- name: Checkout new changes (in case another job already uploaded screenshots)
uses: actions/checkout@v4
# if: steps.dropshotsverify.outputs.newgoldens == 'true'
with:
clean: false
- name: Push new device screenshots if available
uses: stefanzweifel/git-auto-commit-action@4b8a201e31cadd9829df349894b28c54e6c19fe6
# if: steps.dropshotsverify.outputs.newgoldens == 'true'
with:
file_pattern: 'app/src/androidTest/screenshots/*.png'
disable_globbing: true
commit_message: "🤖 Updates instrumented screenshots. API ${{ matrix.api-level }}"
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports-${{ matrix.profile }}-${{ matrix.api-level }}
path: '**/build/reports/androidTests'

@ -62,11 +62,6 @@ android {
excludes.add("/META-INF/{AL2.0,LGPL2.1}")
}
}
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
namespace = "com.google.samples.apps.nowinandroid"
}
@ -128,9 +123,13 @@ dependencies {
androidTestImplementation(projects.core.dataTest)
androidTestImplementation(projects.core.datastoreTest)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.androidx.compose.ui.test)
androidTestImplementation(libs.hilt.android.testing)
androidTestImplementation(libs.kotlin.test)
androidTestImplementation(libs.androidx.test.espresso.device)
androidTestImplementation(libs.androidx.test.uiautomator)
baselineProfile(projects.benchmarks)
}

@ -0,0 +1,338 @@
/*
* Copyright 2024 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
*
* https://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.
*/
package com.google.samples.apps.nowinandroid.ui
import android.graphics.Bitmap
import android.util.Log
import androidx.test.core.app.takeScreenshot
import androidx.test.espresso.device.DeviceInteraction.Companion.setClosedMode
import androidx.test.espresso.device.DeviceInteraction.Companion.setFlatMode
import androidx.test.espresso.device.DeviceInteraction.Companion.setScreenOrientation
import androidx.test.espresso.device.EspressoDevice.Companion.onDevice
import androidx.test.espresso.device.action.ScreenOrientation.LANDSCAPE
import androidx.test.espresso.device.action.ScreenOrientation.PORTRAIT
import androidx.test.espresso.device.common.executeShellCommand
import androidx.test.espresso.device.controller.DeviceMode.CLOSED
import androidx.test.espresso.device.controller.DeviceMode.FLAT
import androidx.test.espresso.device.filter.RequiresDeviceMode
import androidx.test.espresso.device.filter.RequiresDisplay
import androidx.test.espresso.device.sizeclass.HeightSizeClass.Companion.HeightSizeClassEnum
import androidx.test.espresso.device.sizeclass.WidthSizeClass.Companion.WidthSizeClassEnum
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SdkSuppress
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.dropbox.dropshots.Dropshots
import com.google.samples.apps.nowinandroid.MainActivity
import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.After
import org.junit.AfterClass
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
/**
* These tests must be run on the following devices:
* - A phone on API 27 (pixel_5)
* - A foldable on API 33 (pixel_fold)
* - A foldable on API 35 (pixel_fold)
*/
@HiltAndroidTest
@InstrumentedScreenshotTests
class EdgeToEdgeTest {
/**
* Manages the components' state and is used to perform injection on your test
*/
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
/**
* Grant [android.Manifest.permission.POST_NOTIFICATIONS] permission to prevent the
* permissions dialog from showing on top.
*/
@get:Rule(order = 2)
val postNotificationsPermission = GrantPostNotificationsPermissionRule()
@get:Rule(order = 3)
val activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
@get:Rule(order = 4)
val dropshots = Dropshots()
@Before
fun setup() = hiltRule.inject()
@Before
fun enableDemoMode() {
executeShellCommand(
"settings put global development_settings_enabled 1",
)
executeShellCommand("settings put global sysui_demo_allowed 1")
executeShellCommand(
"am broadcast -a com.android.systemui.demo -e command " +
"enter",
)
executeShellCommand(
"am broadcast -a com.android.systemui.demo -e command " +
"notifications -e visible false",
)
executeShellCommand(
"am broadcast -a com.android.systemui.demo -e command " +
"clock -e hhmm 1234",
)
executeShellCommand(
"am broadcast -a com.android.systemui.demo -e command " +
"network -e wifi hide",
)
executeShellCommand(
"am broadcast -a com.android.systemui.demo -e command " +
"network -e mobile hide",
)
executeShellCommand(
"am broadcast -a com.android.systemui.demo -e command " +
"network -e satellite hide",
)
}
@After
fun disableDemoMode() {
exitDemoMode()
executeShellCommand(
"settings put global sysui_demo_allowed 0",
)
executeShellCommand(
"settings put global development_settings_enabled 0",
)
}
private fun exitDemoMode() {
executeShellCommand(
"am broadcast -a com.android.systemui.demo -e command exit",
)
}
companion object {
@JvmStatic
@AfterClass
fun resetDemoMode() {
executeShellCommand("am broadcast -a com.android.systemui.demo -e command exit")
}
}
@RequiresDisplay(WidthSizeClassEnum.COMPACT, HeightSizeClassEnum.MEDIUM)
@SdkSuppress(minSdkVersion = 27, maxSdkVersion = 27)
@Test
fun edgeToEdge_Phone_Api27() {
screenshotSystemBar("edgeToEdge_Phone_systemBar_Api27")
screenshotNavigationBar("edgeToEdge_Phone_navBar_Api27")
}
@RequiresDeviceMode(mode = FLAT)
@RequiresDeviceMode(mode = CLOSED)
@SdkSuppress(minSdkVersion = 33, maxSdkVersion = 33)
@Test
fun edgeToEdge_Foldable_api33() {
runFoldableTests(apiName = "api33")
}
@RequiresDeviceMode(mode = FLAT)
@RequiresDeviceMode(mode = CLOSED)
@SdkSuppress(minSdkVersion = 35, codeName = "VanillaIceCream")
@Test
fun edgeToEdge_Foldable_api35() {
runFoldableTests(apiName = "api35")
}
@RequiresDeviceMode(mode = FLAT)
@RequiresDeviceMode(mode = CLOSED)
@SdkSuppress(minSdkVersion = 35, codeName = "VanillaIceCream")
@Test
fun edgeToEdge_Foldable_api35_tallCutout() {
onDevice().setClosedMode()
forceTallCutout()
exitDemoMode()
enableDemoMode() // Mode change resets demo mode!
screenshotSystemBar("edgeToEdge_Foldable_closed_system_tallCutout_api35")
onDevice().setFlatMode()
exitDemoMode()
forceTallCutout()
enableDemoMode() // Mode change resets demo mode!
screenshotSystemBar("edgeToEdge_Foldable_flat_system_tallCutout_api35")
onDevice().setClosedMode()
resetCutout()
}
// Very flaky:DeviceControllerOperationException: Device could not be set to the
// requested screen orientation.
@RequiresDeviceMode(mode = CLOSED)
@SdkSuppress(minSdkVersion = 35, codeName = "VanillaIceCream")
@Test
fun edgeToEdge_Foldable_api35_landscape() {
onDevice().setClosedMode()
onDevice().setScreenOrientation(LANDSCAPE)
forceThreeButtonNavigation()
exitDemoMode()
enableDemoMode() // This fixes the satellite icon showing up uninvited.
screenshotSideNavigationBar("edgeToEdge_Foldable_landscape_sideNav3button_35")
onDevice().setScreenOrientation(PORTRAIT)
}
private fun runFoldableTests(apiName: String) {
resetCutout()
onDevice().setClosedMode()
screenshotSystemBar("edgeToEdge_Foldable_closed_system_$apiName")
forceThreeButtonNavigation()
screenshotNavigationBar("edgeToEdge_Foldable_closed_nav3button_$apiName")
forceGestureNavigation()
screenshotNavigationBar("edgeToEdge_Foldable_closed_navGesture_$apiName")
onDevice().setFlatMode()
exitDemoMode()
enableDemoMode() // Flat mode resets demo mode!
screenshotSystemBar("edgeToEdge_Foldable_flat_system_$apiName")
forceThreeButtonNavigation()
screenshotNavigationBar("edgeToEdge_Foldable_flat_nav3button_$apiName")
forceGestureNavigation()
screenshotNavigationBar("edgeToEdge_Foldable_flat_navGesture_$apiName")
onDevice().setClosedMode()
}
private fun screenshotSystemBar(screenshotFileName: String) {
waitForWindowUpdate()
// Crop the top, adding extra pixels to check continuity
val bitmap = takeScreenshot().let {
val newHeight = 200
Bitmap.createBitmap(it, 0, 0, it.width, newHeight)
}
assertSnapshot(bitmap, screenshotFileName)
}
private fun screenshotNavigationBar(screenshotFileName: String) {
waitForWindowUpdate()
// Crop the bottom, adding extra pixels to check continuity
val bitmap = takeScreenshot().let {
val newHeight = 200
Bitmap.createBitmap(it, 0, it.height - newHeight, it.width, newHeight)
}
assertSnapshot(bitmap, screenshotFileName)
}
private fun screenshotSideNavigationBar(screenshotFileName: String) {
waitForWindowUpdate()
// Crop the top, adding extra pixels to check continuity
val bitmap = takeScreenshot().let {
Bitmap.createBitmap(it, it.width - 250, 0, 250, it.height)
}
assertSnapshot(bitmap, screenshotFileName)
}
private fun forceThreeButtonNavigation() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
executeShellCommand(
"cmd overlay enable-exclusive " +
"com.android.internal.systemui.navbar.threebutton",
)
}
waitForWindowUpdate()
}
private fun forceGestureNavigation() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
executeShellCommand(
"cmd overlay enable-exclusive " +
"com.android.internal.systemui.navbar.gestural",
)
}
waitForWindowUpdate()
}
private fun forceTallCutout() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
executeShellCommand(
"cmd overlay enable-exclusive " +
"--category com.android.internal.display.cutout.emulation.tall ",
)
}
waitForWindowUpdate()
}
private fun resetCutout() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
executeShellCommand(
"cmd overlay enable-exclusive " +
"--category com.android.internal.display.cutout.emulation.noCutout ",
)
}
waitForWindowUpdate()
}
private fun waitForWindowUpdate() {
// TODO: This works but it's unclear if it's making it wait too long. Investigate.
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.waitForWindowUpdate(
InstrumentationRegistry.getInstrumentation().targetContext.packageName,
8000,
)
}
private fun assertSnapshot(
bitmap: Bitmap,
name: String,
filePath: String? = null,
) {
// Try to assert 3 times
var count = 2
while (true) {
try {
dropshots.assertSnapshot(bitmap, name, filePath)
} catch (e: AssertionError) {
if (count == 0) throw e
count -= 1
Log.i("EdgeToEdgeTest", "Test failed, retrying (count=$count)")
waitForWindowUpdate()
continue
}
break
}
}
private fun executeShellCommand(command: String) {
runOnUiThread {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).executeShellCommand(command)
}
// ADB commands are not synchronized. This sleep was found empirically.
Thread.sleep(20)
}
}

@ -0,0 +1,22 @@
/*
* Copyright 2024 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
*
* https://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.
*/
package com.google.samples.apps.nowinandroid.ui
/**
* TODO: Move to a test module.
*/
annotation class InstrumentedScreenshotTests

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Needed for Dropshots on API 26 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- Needed for Espresso Device -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

@ -34,6 +34,7 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
apply(plugin = "org.jetbrains.kotlin.android")
apply(plugin = "nowinandroid.android.lint")
apply(plugin = "com.dropbox.dependency-guard")
apply(plugin = "com.dropbox.dropshots")
extensions.configure<ApplicationExtension> {
configureKotlinAndroid(this)
@ -41,6 +42,8 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
@Suppress("UnstableApiUsage")
testOptions.animationsDisabled = true
configureGradleManagedDevices(this)
testOptions.emulatorControl.enable = true
// testOptions.unitTests.isIncludeAndroidResources
}
extensions.configure<ApplicationAndroidComponentsExtension> {
configurePrintApksTask(this)

@ -32,6 +32,7 @@ class AndroidFeatureConventionPlugin : Plugin<Project> {
extensions.configure<LibraryExtension> {
testOptions.animationsDisabled = true
testOptions.emulatorControl.enable = true
configureGradleManagedDevices(this)
}

@ -40,6 +40,7 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
defaultConfig.targetSdk = 35
defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testOptions.animationsDisabled = true
testOptions.emulatorControl.enable = true
configureFlavors(this)
configureGradleManagedDevices(this)
// The resource prefix is derived from the module name,

@ -27,12 +27,12 @@ import org.gradle.kotlin.dsl.invoke
internal fun configureGradleManagedDevices(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
val pixel4 = DeviceConfig("Pixel 4", 30, "aosp-atd")
val pixel4 = DeviceConfig("Pixel 4", 27, "aosp")
val pixel6 = DeviceConfig("Pixel 6", 31, "aosp")
val pixelC = DeviceConfig("Pixel C", 30, "aosp-atd")
val pixelC = DeviceConfig("Pixel C", 30, "aosp")
val allDevices = listOf(pixel4, pixel6, pixelC)
val ciDevices = listOf(pixel4, pixelC)
val ciDevices = listOf(pixel4, pixel6, pixelC)
commonExtension.testOptions {
managedDevices {

@ -52,6 +52,7 @@ plugins {
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.dependencyGuard) apply false
alias(libs.plugins.dropshots) apply false
alias(libs.plugins.firebase.crashlytics) apply false
alias(libs.plugins.firebase.perf) apply false
alias(libs.plugins.gms) apply false

@ -54,12 +54,14 @@ kotlin.code.style=official
# Disable build features that are enabled by default,
# https://developer.android.com/build/releases/gradle-plugin#default-changes
android.defaults.buildfeatures.resvalues=false
android.defaults.buildfeatures.shaders=false
# Run Roborazzi screenshot tests with the local tests
roborazzi.test.verify=true
# Espresso Device
android.experimental.androidTest.enableEmulatorControl=true
# Prevent uninstall app after instrumented tests
# https://issuetracker.google.com/issues/295039976
android.injected.androidTest.leaveApksInstalledAfterRun=true

@ -15,6 +15,7 @@ androidxCore = "1.15.0"
androidxCoreSplashscreen = "1.0.1"
androidxDataStore = "1.1.1"
androidxEspresso = "3.6.1"
androidxEspressoDevice = "1.0.0-beta01"
androidxHiltNavigationCompose = "1.2.0"
androidxLifecycle = "2.8.7"
androidxLintGradle = "1.0.0-alpha03"
@ -32,6 +33,7 @@ androidxWindowManager = "1.3.0"
androidxWork = "2.10.0"
coil = "2.7.0"
dependencyGuard = "0.5.0"
dropshots = "0.4.2"
firebaseBom = "33.7.0"
firebaseCrashlyticsPlugin = "3.0.2"
firebasePerfPlugin = "1.4.2"
@ -102,6 +104,7 @@ androidx-navigation-testing = { group = "androidx.navigation", name = "navigatio
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidxProfileinstaller" }
androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" }
androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidxEspresso" }
androidx-test-espresso-device = { group = "androidx.test.espresso", name = "espresso-device", version.ref = "androidxEspressoDevice" }
androidx-test-ext = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxTestExt" }
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" }
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" }
@ -170,6 +173,7 @@ android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
baselineprofile = { id = "androidx.baselineprofile", version.ref = "androidxMacroBenchmark"}
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
dependencyGuard = { id = "com.dropbox.dependency-guard", version.ref = "dependencyGuard" }
dropshots = { id = "com.dropbox.dropshots", version.ref = "dropshots" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsPlugin" }
firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "firebasePerfPlugin" }
gms = { id = "com.google.gms.google-services", version.ref = "gmsPlugin" }

Loading…
Cancel
Save