Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions KeepAnnotationsSample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,57 @@ This examples shows the use of the `androidx.annotation.keep` Gradle plugin.

- This programmatically generates `keep` rules when an `app` / `android library` / `java library` module uses `androidx.annotation:annotation-keep`.


## Testing

```kotlin
### Android apps and Library Modules

// For app modules
```bash

// App Modules

./gradlew <variantName>ExtractKeepRules

// For android library modules
// For Android Library Modules

./gradlew <variantName>KeepRulesTransformAar
```

### Java / Kotlin Library Modules

For Java Libraries, you need to do the following.

* Apply the Gradle Plugin.

```kotlin
plugins {
alias(libs.plugins.androidx.annotation.keep)
}
```

The generated keep rules end up in `build/generated/variantName/androidx.annotation.keep.rules.pro`.
* Register a new task that can generate the artifact with keep rules.

```kotlin
val outputJar = project.tasks.named<org.gradle.jvm.tasks.Jar>("jar").flatMap { task ->
task.archiveFile
}

val outputFile = outputJar.map { file -> "keep${file.asFile.name.uppercaseFirstChar()}" }

// Register the JAR transform for extracting keep rules.
val artifactTask = annotationKeep.registerJavaArchiveTransform(
taskName = "keepRulesJar",
input = outputJar,
fileName = outputFile
)
```

* Register the output artifact for publication.

```kotlin
// Register the new artifact.
project.configurations.configureEach {
if (name == "apiElements" || name == "runtimeElements") {
outgoing.artifact(artifactTask)
}
}
```
2 changes: 2 additions & 0 deletions KeepAnnotationsSample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.androidx.annotation.keep) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
}
7 changes: 7 additions & 0 deletions KeepAnnotationsSample/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ lifecycleRuntimeKtx = "2.11.0"
activityCompose = "1.13.0"
kotlin = "2.4.20"
composeBom = "2026.08.00"
appcompat = "1.6.1"
material = "1.10.0"
jetbrainsKotlinJvm = "2.4.20"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
Expand All @@ -26,8 +29,12 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
androidx-annotation-keep = { id = "androidx.annotation.keep", version.ref = "annotationKeep" }
android-library = { id = "com.android.library", version.ref = "agp" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
1 change: 1 addition & 0 deletions KeepAnnotationsSample/java-library/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
42 changes: 42 additions & 0 deletions KeepAnnotationsSample/java-library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import org.gradle.kotlin.dsl.support.uppercaseFirstChar

plugins {
id("java-library")
alias(libs.plugins.jetbrains.kotlin.jvm)
alias(libs.plugins.androidx.annotation.keep)
}

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

val outputJar = project.tasks.named<org.gradle.jvm.tasks.Jar>("jar").flatMap { task ->
task.archiveFile
}

val outputFile = outputJar.map { file -> "keep${file.asFile.name.uppercaseFirstChar()}" }

// Register the JAR transform for extracting keep rules.
val artifactTask = annotationKeep.registerJavaArchiveTransform(
taskName = "keepRulesJar",
input = outputJar,
fileName = outputFile
)

// Register the new artifact.
project.configurations.configureEach {
if (name == "apiElements" || name == "runtimeElements") {
outgoing.artifact(artifactTask)
}
}

dependencies {
implementation(libs.androidx.annotation.keep)
}

kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.example.java.library

import androidx.annotation.keep.UsesReflectionToConstruct

interface PlainLocator {
companion object {
@UsesReflectionToConstruct(classConstant = PlainLocatorImpl::class)
fun load(): PlainLocator {
val klass = Class.forName(PlainLocatorImpl::class.java.name)
@Suppress("DEPRECATION")
return klass.newInstance() as PlainLocator
}
}
}

private class PlainLocatorImpl : PlainLocator
1 change: 1 addition & 0 deletions KeepAnnotationsSample/library/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
29 changes: 29 additions & 0 deletions KeepAnnotationsSample/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.androidx.annotation.keep)
}

android {
namespace = "org.example.library"
compileSdk {
version = release(37)
}
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

dependencies {
implementation(libs.androidx.annotation.keep)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.junit)
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.example.library

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.example.library.test", appContext.packageName)
}
}
4 changes: 4 additions & 0 deletions KeepAnnotationsSample/library/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.example.library

import androidx.annotation.keep.UsesReflectionToConstruct

interface LibraryResourceLocator {
companion object {
@UsesReflectionToConstruct(classConstant = LibraryResourceLocatorImpl::class)
fun load(): LibraryResourceLocator {
val klass = Class.forName(LibraryResourceLocatorImpl::class.java.name)
@Suppress("DEPRECATION")
return klass.newInstance() as LibraryResourceLocator
}
}
}

private class LibraryResourceLocatorImpl : LibraryResourceLocator
12 changes: 12 additions & 0 deletions KeepAnnotationsSample/library/src/main/keepRules/rules.keep
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Add project specific R8 rules here.
# AGP will combine all keep rule files in src/main/keepRules to pass to R8
#
# For more details, see
# https://d.android.com/r/tools/r8/keep-rules

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.example.library

import org.junit.Test

import org.junit.Assert.*

/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
6 changes: 4 additions & 2 deletions KeepAnnotationsSample/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
maven {
url = uri("https://androidx.dev/snapshots/builds/16303799/artifacts/repository")
url = uri("https://androidx.dev/snapshots/builds/16312043/artifacts/repository")
}
}
}
Expand All @@ -23,7 +23,7 @@ dependencyResolutionManagement {
google()
mavenCentral()
maven {
url = uri("https://androidx.dev/snapshots/builds/16303799/artifacts/repository")
url = uri("https://androidx.dev/snapshots/builds/16312043/artifacts/repository")
content {
includeGroupByRegex("androidx\\.annotation.*")
}
Expand All @@ -34,3 +34,5 @@ dependencyResolutionManagement {

rootProject.name = "Example"
include(":app")
include(":library")
include(":java-library")
Loading