diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/build/agp/agp-9-upgrade/SKILL.md b/build/agp/agp-9-upgrade/SKILL.md new file mode 100644 index 0000000..84eb12c --- /dev/null +++ b/build/agp/agp-9-upgrade/SKILL.md @@ -0,0 +1,89 @@ +--- +name: agp-9-upgrade +description: Upgrades, or migrates, an Android project to use Android Gradle Plugin + (AGP) version 9 +license: Complete terms in LICENSE.txt +metadata: + author: Google LLC + keywords: + - Android Gradle Plugin 9 + - AGP 9 + - AGP Upgrade + - AGP Migration + - New AGP DSL + - Migrate to built-in Kotlin +--- + +## Migration guide + +See the [AGP 9 migration guide](references/android/build/releases/agp-9-0-0-release-notes.md) for the major changes, many +breaking, in AGP 9 compared to AGP 8. + +## Requirements + +If the user requests to update or migrate to AGP 9, first check the AGP version +used in the project. If it is lower than 9, stop and ask the user to run the AGP +Upgrade Assistant in Android Studio, and confirm when done. + +Each version of AGP has its own set of compatibilities with other tools, such as +Gradle, JDK, and Kotlin. The release notes for each of these versions will +include a **Compatibility** table indicating the minimum versions for these +tools. + +## Steps + +If AGP is already at 9 or higher, then do the following: + +### Step 1: Migrate to built-in Kotlin. + +See [the guide](references/android/build/migrate-to-built-in-kotlin.md) for detailed information. + +### Step 2. Migrate to the new AGP DSL. + +See [the guide](references/android/build/releases/agp-9-0-0-release-notes.md) for detailed information. + +See also [gradle-recipes](references/recipes.md) for examples on how to migrate old code to code +that is compatible with AGP 9 and the new DSL. + +### Step 3. Migrate kapt to KSP or legacy-kapt + +If KSP (`com.google.devtools.ksp`) is used in the project, ensure it is on +version 2.3.6 or higher. + +See [KSP, kapt, and legacy-kapt](references/ksp-kapt.md) for detailed information. + +### Step 4. BuildConfig + +If any Android module contains custom BuildConfig fields, see [BuildConfig](references/buildconfig.md) +for detailed information. + +### Step 5. Update gradle.properties + +After the migration, check gradle.properties. Remove the following flags: + +1. android.builtInKotlin +2. android.newDsl +3. android.uniquePackageNames +4. android.enableAppCompileTimeRClass + +Additionally, delete all temporary files you've created. + +## Guidelines + +- Never write or run python scripts. +- Only search the Gradle dependency cache when inspecting external dependencies, and only as a last resort. +- Never add `android.disallowKotlinSourceSets=false` to `gradle.properties`. +- When verifying changes, don't run the `clean` task. This is a waste of time. + +## Verification + +After migration, verify the following: + +1. Gradle IDE sync succeeds. +2. `./gradlew help` succeeds. +3. `./gradlew build --dry-run` succeeds. + +## Troubleshooting + +Paparazzi v2.0.0-alpha04 and lower versions have issues with AGP 9. See +[references/paparazzi-gradle-9.md](references/paparazzi-gradle-9.md) for details. \ No newline at end of file diff --git a/build/agp/agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md b/build/agp/agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md new file mode 100644 index 0000000..7306e7b --- /dev/null +++ b/build/agp/agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md @@ -0,0 +1,416 @@ +Android Gradle plugin 9.0 introduces built-in Kotlin support and enables it +by default. That means you no longer have to apply the +`org.jetbrains.kotlin.android` (or `kotlin-android`) plugin in your build files +to compile Kotlin source files. +With built-in Kotlin, your build files are simpler and you can avoid +compatibility issues between AGP and the `kotlin-android` plugin. + +> [!NOTE] +> **Note:** Built-in Kotlin replaces the `kotlin-android` plugin only. If you are writing a Kotlin Multiplatform (KMP) library module, you still need to apply the `org.jetbrains.kotlin.multiplatform` plugin and the [`com.android.kotlin.multiplatform.library`](https://developer.android.com/kotlin/multiplatform/plugin) plugin. Also, using the `org.jetbrains.kotlin.multiplatform` plugin together with the `com.android.library` or `com.android.application` plugin is no longer allowed when built-in Kotlin is enabled. + +## Enable built-in Kotlin + +You need AGP 9.0 or higher to have built-in Kotlin support. +AGP 9.0 already enables built-in Kotlin for all your modules where you apply +AGP, so you don't need to do anything to enable it. However, if you previously +[opted out of built-in Kotlin](https://developer.android.com/build/migrate-to-built-in-kotlin#opt-out-of-built-in-kotlin) by setting `android.builtInKotlin=false` +in the `gradle.properties` file, you need to remove that setting or set it to +`true`. + +> [!NOTE] +> **Note:** You can also enable built-in Kotlin for [one module at a time](https://developer.android.com/build/migrate-to-built-in-kotlin#module-by-module-migration). + +Built-in Kotlin requires some changes to your project, so after you +have built-in Kotlin enabled, follow the next steps to migrate your project. + +## Migration steps + +After you upgrade your project from an older AGP version to AGP 9.0 or after +you manually [enable built-in Kotlin](https://developer.android.com/build/migrate-to-built-in-kotlin#enable-built-in-kotlin), you might see the following error +message: + + Failed to apply plugin 'org.jetbrains.kotlin.android'. + > Cannot add extension with name 'kotlin', as there is an extension already registered with that name. + +...or + + Failed to apply plugin 'com.jetbrains.kotlin.android' + > The 'org.jetbrains.kotlin.android' plugin is no longer required for Kotlin support since AGP 9.0. + +This error occurs because built-in Kotlin requires some changes to your project. +To resolve this error, follow these steps: + +> [!NOTE] +> **Note:** If you're not yet ready to migrate your project, you can also [opt out of built-in Kotlin](https://developer.android.com/build/migrate-to-built-in-kotlin#opt-out-of-built-in-kotlin). + +1. [Remove the `kotlin-android` plugin](https://developer.android.com/build/migrate-to-built-in-kotlin#migration-steps-remove-kotlin-android-plugin) +2. [Migrate the `kotlin-kapt` plugin if necessary](https://developer.android.com/build/migrate-to-built-in-kotlin#migration-steps-migrate-kotlin-kapt-plugin) +3. [Migrate the `android.kotlinOptions{}` DSL if necessary](https://developer.android.com/build/migrate-to-built-in-kotlin#migration-steps-migrate-kotlin-options) +4. [Migrate the `kotlin.sourceSets{}` DSL if necessary](https://developer.android.com/build/migrate-to-built-in-kotlin#migration-steps-migrate-kotlin-source-sets) + +### 1. Remove the `kotlin-android` plugin + +Remove the `org.jetbrains.kotlin.android` (or `kotlin-android`) plugin from +the module-level build files where you apply it. +The exact code to remove depends on +whether you use [version catalogs](https://docs.gradle.org/current/userguide/version_catalogs.html) to declare plugins. + +### With version catalogs + +### Kotlin + +```kotlin +// Module-level build file +plugins { + alias(libs.plugins.kotlin.android) +} +``` + +### Groovy + +```groovy +// Module-level build file +plugins { + alias(libs.plugins.kotlin.android) +} +``` + +### No version catalogs + +### Kotlin + +```kotlin +// Module-level build file +plugins { + id("org.jetbrains.kotlin.android") +} +``` + +### Groovy + +```groovy +// Module-level build file +plugins { + id 'org.jetbrains.kotlin.android' +} +``` + +Then, remove the plugin from your top-level build file: + +### With version catalogs + +### Kotlin + +```kotlin +// Top-level build file +plugins { + alias(libs.plugins.kotlin.android) apply false +} +``` + +### Groovy + +```groovy +// Top-level build file +plugins { + alias(libs.plugins.kotlin.android) apply false +} +``` + +### No version catalogs + +### Kotlin + +```kotlin +// Top-level build file +plugins { + id("org.jetbrains.kotlin.android") version "KOTLIN_VERSION" apply false +} +``` + +### Groovy + +```groovy +// Top-level build file +plugins { + id 'org.jetbrains.kotlin.android' version 'KOTLIN_VERSION' apply false +} +``` + +If you use version catalogs, also remove the plugin definition from the +version catalog TOML file (usually `gradle/libs.versions.toml`): + +```toml +[plugins] +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "KOTLIN_VERSION" } +``` + +### 2. Migrate the `kotlin-kapt` plugin if necessary + +The `org.jetbrains.kotlin.kapt` (or `kotlin-kapt`) plugin is incompatible with +built-in Kotlin. If you use `kapt`, we recommend that you +[migrate your project to KSP](https://developer.android.com/build/migrate-to-ksp). + +If you can't migrate to KSP yet, replace the `kotlin-kapt` plugin with the +`com.android.legacy-kapt` plugin, using the same version as your Android Gradle +plugin. + +For example, with version catalogs, update your version catalog TOML +file as follows: + +```toml +[plugins] +android-application = { id = "com.android.application", version.ref = "AGP_VERSION" } + +# Add the following plugin definition +legacy-kapt = { id = "com.android.legacy-kapt", version.ref = "AGP_VERSION" } + +# Remove the following plugin definition +kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "KOTLIN_VERSION" } +``` + +Then, update your build files: + +### Kotlin + +```kotlin +// Top-level build file +plugins { + alias(libs.plugins.legacy.kapt) apply false + alias(libs.plugins.kotlin.kapt) apply false +} +``` + +### Groovy + +```groovy +// Top-level build file +plugins { + alias(libs.plugins.legacy.kapt) apply false + alias(libs.plugins.kotlin.kapt) apply false +} +``` + +### Kotlin + +```kotlin +// Module-level build file +plugins { + alias(libs.plugins.legacy.kapt) + alias(libs.plugins.kotlin.kapt) +} +``` + +### Groovy + +```groovy +// Module-level build file +plugins { + alias(libs.plugins.legacy.kapt) + alias(libs.plugins.kotlin.kapt) +} +``` + +> [!NOTE] +> **Note:** If you declare the `kotlin-kapt` plugin in the `plugins{}` block as `kotlin("kapt") version ""`, then remove that line instead. + +### 3. Migrate the `android.kotlinOptions{}` DSL if necessary + +If you use the `android.kotlinOptions{}` DSL, you need to +migrate it to the [`kotlin.compilerOptions{}`](https://kotlinlang.org/docs/gradle-compiler-options.html#migrate-from-kotlinoptions-to-compileroptions) DSL. + +For example, update this code: + +### Kotlin + +```kotlin +android { + kotlinOptions { + languageVersion = "2.0" + jvmTarget = "11" + } +} +``` + +### Groovy + +```groovy +android { + kotlinOptions { + languageVersion = "2.0" + jvmTarget = "11" + } +} +``` + +...to the new DSL: + +### Kotlin + +```kotlin +kotlin { + compilerOptions { + languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0 + // Optional: Set jvmTarget + // jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 + } +} +``` + +### Groovy + +```groovy +kotlin { + compilerOptions { + languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0 + // Optional: Set jvmTarget + // jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 + } +} +``` + +> [!NOTE] +> **Note:** With built-in Kotlin, you don't need to set `kotlin.compilerOptions.jvmTarget` because its value defaults to `android.compileOptions.targetCompatibility`. + +### 4. Migrate the `kotlin.sourceSets{}` DSL if necessary + +When you use the `kotlin-android` plugin, AGP lets you add additional Kotlin +source directories using either the [`android.sourceSets{}`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/AndroidSourceSet) DSL or the +[`kotlin.sourceSets{}`](https://kotlinlang.org/api/kotlin-gradle-plugin/kotlin-gradle-plugin-api/org.jetbrains.kotlin.gradle.plugin/-kotlin-source-set/) DSL. +With the `android.sourceSets{}` DSL, you can add the directories to either the +`AndroidSourceSet.kotlin` set or the `AndroidSourceSet.java` set. + +With built-in Kotlin, the only supported option is to add the directories to the +`AndroidSourceSet.kotlin` set using the `android.sourceSets{}` DSL. +If you use unsupported options, migrate them as follows: + +### Kotlin + +```kotlin +# Adding Kotlin source directories to kotlin.sourceSets is not supported +kotlin.sourceSets.named("main") { + kotlin.srcDir("additionalSourceDirectory/kotlin") +} + +# Adding Kotlin source directories to AndroidSourceSet.java is also not supported +android.sourceSets.named("main") { + java.directories += "additionalSourceDirectory/kotlin" +} + +# Add Kotlin source directories to AndroidSourceSet.kotlin +android.sourceSets.named("main") { + kotlin.directories += "additionalSourceDirectory/kotlin" +} +``` + +### Groovy + +```groovy +# Adding Kotlin source directories to kotlin.sourceSets is not supported +kotlin.sourceSets.named("main") { + kotlin.srcDir("additionalSourceDirectory/kotlin") +} + +# Adding Kotlin source directories to AndroidSourceSet.java is also not supported +android.sourceSets.named("main") { + java.directories.add("additionalSourceDirectory/kotlin") +} + +# Add Kotlin source directories to AndroidSourceSet.kotlin +android.sourceSets.named("main") { + kotlin.directories.add("additionalSourceDirectory/kotlin") +} +``` + +If you want to add a Kotlin source directory to a specific variant or if the +directory is generated by a task, you can use the +[`addStaticSourceDirectory`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/SourceDirectories#addStaticSourceDirectory(kotlin.String)) or [`addGeneratedSourceDirectory`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/SourceDirectories#addGeneratedSourceDirectory(org.gradle.api.tasks.TaskProvider,kotlin.Function1)) methods +in the [variant API](https://developer.android.com/build/extend-agp#variant-api-artifacts-tasks): + +### Kotlin + +```kotlin +androidComponents.onVariants { variant -> + variant.sources.kotlin!!.addStaticSourceDirectory("additionalSourceDirectory/kotlin") + variant.sources.kotlin!!.addGeneratedSourceDirectory(TASK_PROVIDER, TASK_OUTPUT) +} +``` + +### Groovy + +```groovy +androidComponents.onVariants { variant -> + variant.sources.kotlin!!.addStaticSourceDirectory("additionalSourceDirectory/kotlin") + variant.sources.kotlin!!.addGeneratedSourceDirectory(TASK_PROVIDER, TASK_OUTPUT) +} +``` + +## Report issues + +If you encounter issues after completing the previous steps, +review the known issues in [issue #438678642](https://issuetracker.google.com/438678642) and give us +feedback if needed. + +## Opt out of built-in Kotlin + +If you are unable to migrate your project to use built-in Kotlin, set +`android.builtInKotlin=false` in the `gradle.properties` file to temporarily +disable it. +When you do that, the build shows a warning reminding you to migrate to built-in +Kotlin as you won't be able to disable built-in Kotlin in AGP 10.0. + +> [!NOTE] +> **Note:** You also need to set `android.newDsl=false` to opt out of the [new DSL](https://developer.android.com/r/tools/new-dsl) because the `kotlin-android` plugin is not compatible with it. + +Once you're ready to migrate your project, [enable built-in Kotlin](https://developer.android.com/build/migrate-to-built-in-kotlin#enable-built-in-kotlin) +and follow the [migration steps](https://developer.android.com/build/migrate-to-built-in-kotlin#migration-steps). + +## Module-by-module migration + +The `android.builtInKotlin` Gradle property lets you enable or disable built-in +Kotlin for all your modules where you apply AGP. + +If migrating all your modules at once is challenging, you can migrate one module +at a time: + +1. Set `android.builtInKotlin=false` in the `gradle.properties` file to + disable built-in Kotlin for all modules. + +2. Apply the `com.android.built-in-kotlin` plugin to the module + you want to enable built-in Kotlin, using the same version as your + Android Gradle plugin. + +3. Follow the previous [migration steps](https://developer.android.com/build/migrate-to-built-in-kotlin#migration-steps) to migrate this module to + built-in Kotlin. + +4. Once you've migrated all your modules, remove the + `android.builtInKotlin=false` setting in `gradle.properties` + and the `com.android.built-in-kotlin` plugin in your build files. + +## Option to selectively disable built-in Kotlin + +Android Gradle plugin 9.0 enables built-in Kotlin for all modules where it is +applied. +We recommend disabling built-in Kotlin selectively for modules that don't have +Kotlin sources in large projects. +This removes both the Kotlin compilation task, which has a small build +performance cost, and the automatic dependency on the Kotlin standard library. + +To disable built-in Kotlin for a module, +set `enableKotlin = false` in that module's build file: + +### Kotlin + +```kotlin +android { + enableKotlin = false +} +``` + +### Groovy + +```groovy +android { + enableKotlin = false +} +``` \ No newline at end of file diff --git a/build/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md b/build/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md new file mode 100644 index 0000000..b6a6e56 --- /dev/null +++ b/build/agp/agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md @@ -0,0 +1,551 @@ +
+ +Android Gradle plugin 9.0 is a major release that brings API and behavior +changes. + +To update to Android Gradle plugin 9.0.1, +use the [Android Gradle plugin Upgrade Assistant](https://developer.android.com/build/agp-upgrade-assistant). + +The AGP upgrade assistant helps preserve existing behaviors when upgrading your +project whenever appropriate, allowing you to upgrade your project to use AGP +9.0 even if you're not ready to adopt all the new defaults in AGP 9.0. + +## Compatibility + +The maximum API level that Android Gradle plugin 9.0 supports is API level 36.1. +Here is other compatibility info: + + +| | Minimum version | Default version | Notes | +|---:|:---:|:---:|:---:| +| Gradle | 9.1.0 | 9.1.0 | To learn more, see [updating Gradle](https://developer.android.com/build/releases/gradle-plugin?buildsystem=ndk-build#updating-gradle). | +| SDK Build Tools | 36.0.0 | 36.0.0 | [Install](https://developer.android.com/studio/intro/update#sdk-manager) or [configure](https://developer.android.com/tools/releases/build-tools) SDK Build Tools. | +| NDK | N/A | 28.2.13676358 | [Install](https://developer.android.com/studio/projects/install-ndk#specific-version) or [configure](https://developer.android.com/studio/projects/install-ndk#apply-specific-version) a different version of the NDK. | +| JDK | 17 | 17 | To learn more, see [setting the JDK version](https://developer.android.com/studio/intro/studio-config#jdk). | + +
+ +## The `android` DSL classes now only implement the new public interfaces + +Over the last several years, we have introduced +[new interfaces](https://developer.android.com/reference/tools/gradle-api) for our DSL and API in order to +better control which APIs are public. AGP versions +7.x and 8.x still used the old DSL types (for example `BaseExtension`) which +also implemented the new public interfaces, in order to maintain compatibility +as work progressed on the interfaces. + +AGP 9.0 uses our new DSL interfaces exclusively, +and the implementations have changed to new types that are fully hidden. This +also removes access to the old, deprecated variant API. + +To update to AGP 9.0, you might need to do the following: + +- **Ensure your project is compatible with [built-in + Kotlin](https://developer.android.com/build/releases/agp-9-0-0-release-notes#android-gradle-plugin-built-in-kotlin):** The `org.jetbrains.kotlin.android` plugin is not compatible with the new DSL. +- **Switch KMP projects to the + [Android Gradle Library Plugin for KMP](https://developer.android.com/kotlin/multiplatform/plugin):** + Using the `org.jetbrains.kotlin.multiplatform` plugin in the same Gradle + subproject as the `com.android.library` and `com.android.application` plugins + is not compatible with the new DSL. + + > [!NOTE] + > **Note:** The new KMP integration does not support using KMP and the Android Application plugin in the same Gradle subproject. To migrate, extract your Android app to a separate subproject. + +- **Update your build files:** + While the change of interfaces is meant to keep + the DSL as similar as possible, there might be + [some small changes](https://developer.android.com/build/releases/agp-9-0-0-release-notes#android-gradle-plugin-changed-dsl). + +- **Update your custom build logic to reference the new DSL and API:** + Replace any references to the internal DSL with the public DSL interfaces. + In most cases this will be a one-to-one replacement. + Replace any use of the `applicationVariants` and similar APIs with the new + [`androidComponents` API](https://developer.android.com/build/extend-agp#variant-api-artifacts-tasks). + This might be more complex, as the `androidComponents` API + is designed to be more stable to keep plugins compatible longer. Check our + [Gradle Recipes](https://github.com/android/gradle-recipes/tree/agp-9.0) + for examples. + +- **Update third-party plugins:** + Some third-party plugins might still depend on interfaces or APIs that are + no longer exposed. Migrate to versions of those plugins which are compatible + with AGP 9.0. + +The switch to the new DSL interfaces prevents plugins and Gradle build scripts +using various deprecated APIs, including: + +| Deprecated API in the `android` block | Function | Replacement | +|---|---|---| +| `applicationVariants`, `libraryVariants`, `testVariants`, and `unitTestVariants` | Extension points for plugins to add new functionality to AGP. | Replace this with the [`androidComponents.onVariants`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/AndroidComponentsExtension#onVariants(com.android.build.api.variant.VariantSelector,kotlin.Function1)) API, for example: ```kotlin androidComponents { onVariants() { variant -> variant.signingConfig .enableV1Signing.set(false) } } ``` There might not be a direct replacement for all previous APIs. [File an issue](https://developer.android.com/studio/report-bugs) if there is a use case that is not covered by the new variant APIs. | +| `variantFilter` | Allows selected variants to be disabled. | Replace this with the [`androidComponents.beforeVariants`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/AndroidComponentsExtension#beforeVariants(com.android.build.api.variant.VariantSelector,kotlin.Function1))) API, for example: ```kotlin androidComponents { beforeVariants( selector() .withBuildType("debug") .withFlavor("color", "blue") ) { variantBuilder -> variantBuilder.enable = false } } ``` | +| `deviceProvider` and `testServer` | Registration of custom test environments for running tests against Android devices and emulators. | Switch to [Gradle-managed devices](https://developer.android.com/studio/test/gradle-managed-devices). | +| `sdkDirectory`, `ndkDirectory`, `bootClasspath`, `adbExecutable`, and `adbExe` | Using various components of the Android SDK for custom tasks. | Switch to [`androidComponents.sdkComponents`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/SdkComponents). | +| `registerArtifactType`, `registerBuildTypeSourceProvider`, `registerProductFlavorSourceProvider`, `registerJavaArtifact`, `registerMultiFlavorSourceProvider`, and `wrapJavaSourceSet` | Obsolete functionality mostly related to the handling of generated sources in Android Studio, which stopped working in AGP 7.2.0. | There is no direct replacement for these APIs. | +| `dexOptions` | Obsolete settings related to the `dx` tool, which has been replaced by [`d8`](https://developer.android.com/tools/d8). None of the settings have had any effect since Android Gradle plugin 7.0. | There is no direct replacement. | +| `generatePureSplits` | Generate configuration splits for instant apps. | The ability to ship configuration splits is now built in to Android app bundles. | +| `aidlPackagedList` | AIDL files to package in the AAR to expose it as API for libraries and apps that depend on this library. | This is still exposed on [`LibraryExtension`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/LibraryExtension) but not on the other extension types. | + +If you update to AGP 9.0 and see the following error message, it means that +your project is still referencing some of the old types: + + java.lang.ClassCastException: class com.android.build.gradle.internal.dsl.ApplicationExtensionImpl$AgpDecorated_Decorated + cannot be cast to class com.android.build.gradle.BaseExtension + +If you are blocked by incompatible third-party plugins, you can opt out and +get back the old implementations for the DSL, as well as the old variant API. +While doing this, the new interfaces are also available, and you can still +update your own build logic to the new API. To opt out, include this line in +your `gradle.properties` file: + + android.newDsl=false + +The previous classes are marked as deprecated in AGP 9.0. This means projects +that opt out of the `newDsl` flag will see deprecation warnings, including on +the `android` block itself. + +> [!CAUTION] +> **Caution:** The ability to opt-out will be removed in AGP 10.0 (mid-2026). + +You can also start upgrading to the new APIs before upgrading to AGP 9.0. The +new interfaces have been present for many AGP versions and so you can have a mix +of new and old. The [AGP API reference docs](https://developer.android.com/reference/tools/gradle-api) show the API +surface for each AGP version, and when each class, method and field was +added. + +We're reaching out to the authors of commonly used plugins to help them adapt +and release plugins that are fully compatible with the new modes, and will +continue to enhance the AGP Upgrade Assistant in Android Studio to guide you +through the migration. + +If you find that the new DSL or Variant API are missing capabilities or +features, please file an [issue](https://issuetracker.google.com/issues/new?component=192708&template=840533) as soon as possible. + +## Built-in Kotlin + +Android Gradle plugin 9.0 introduces built-in Kotlin support and enables it +by default. That means you no longer have to apply the +`org.jetbrains.kotlin.android` (or `kotlin-android`) plugin in your build files +to compile Kotlin source files. +This simplifies the Kotlin integration with AGP, avoids the use of +deprecated APIs, and improves performance in some cases. + +Therefore, when you upgrade your project to AGP 9.0, you need to also +[migrate to built-in Kotlin](https://developer.android.com/build/migrate-to-built-in-kotlin) or [opt out](https://developer.android.com/build/migrate-to-built-in-kotlin#opt-out-of-built-in-kotlin). + +You can also [selectively disable built-in Kotlin support](https://developer.android.com/build/migrate-to-built-in-kotlin#selectively-disable) for Gradle +subprojects that don't have Kotlin sources. + +## Runtime dependency on Kotlin Gradle plugin + +To provide [built-in Kotlin](https://developer.android.com/build/migrate-to-built-in-kotlin) support, Android Gradle plugin 9.0 now has a +runtime dependency on Kotlin Gradle plugin (KGP) 2.2.10. +That means you no longer have to declare a KGP version, and if you use a KGP +version lower than 2.2.10, Gradle will automatically upgrade your KGP version to +2.2.10. +Likewise, if you use a KSP version lower than 2.2.10-2.0.2, AGP will upgrade it +to 2.2.10-2.0.2 to match the KGP version. + +### Upgrade to a higher KGP version + +To use a higher version of KGP or KSP, add the following to your top-level build +file: + + buildscript { + dependencies { + // For KGP + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:KGP_VERSION") + + // For KSP + classpath("com.google.devtools.ksp:symbol-processing-gradle-plugin:KSP_VERSION") + } + } + +### Downgrade to a lower KGP version + +You can only downgrade the KGP version if you've +[opted out of built-in Kotlin](https://developer.android.com/build/migrate-to-built-in-kotlin#opt-out-of-built-in-kotlin). +This is because AGP 9.0 enables built-in Kotlin by default, and built-in Kotlin +requires KGP 2.2.10 or higher. + +To use a lower version of KGP or KSP, declare that version in your top-level +build file using a [strict version](https://docs.gradle.org/current/userguide/dependency_versions.html#sec:strict-version) declaration: + + buildscript { + dependencies { + // For KGP + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") { + version { strictly("KGP_VERSION") } + } + + // For KSP + classpath("com.google.devtools.ksp:symbol-processing-gradle-plugin") { + version { strictly("KSP_VERSION") } + } + } + } + +Note that the minimum KGP version you can downgrade to is 2.0.0. + +> [!CAUTION] +> **Caution:** When you downgrade the KGP version, your project might not be compatible with future minor releases of AGP 9, and it might not work with test fixtures. + +## IDE support for test fixtures + +AGP 9.0 brings full Android Studio IDE support for +[test fixtures](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/TestFixtures). + +## Fused Library Plugin + +The Fused Library Plugin (Preview) lets you publish multiple +libraries as a single Android Library AAR. This can make it easier for your +users to depend on your published artifacts. + +For information about getting started, see +[Publish multiple Android libraries as one with Fused Library](https://developer.android.com/build/publish-library/fused-library). + +## Behavior changes + +Android Gradle plugin 9.0 has the following new behaviors: + +| Behavior | Recommendation | +|---|---| +| Android Gradle plugin 9.0 uses NDK version `r28c` by default. | Consider specifying the NDK version you want to use explicitly. | +| Android Gradle plugin 9.0 by default requires consumers of a library to use the same or higher compile SDK version. | Use the same or higher compile SDK when consuming a library. If this is not possible, or you want to give consumers of a library you publish more time to switch, set [`AarMetadata.minCompileSdk`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/AarMetadata#minCompileSdk()) explicitly. | + +AGP 9.0 includes updates to the following Gradle properties' defaults. +This gives you the choice to preserve the AGP 8.13 behavior when upgrading: + +| Property | Function | Change from AGP 8.13 to AGP 9.0 | Recommendation | +|---|---|---|---| +| `android.newDsl` | Use the new DSL interfaces, without exposing the legacy implementations of the `android` block. This also means the legacy variant API, such as `android.applicationVariants` is no longer accessible. | `false` → `true` | You can opt out by setting `android.newDsl=false`. Once all plugins and build logic your project uses are compatible, remove the opt out. | +| `android.builtInKotlin` | Enables [built-in Kotlin](https://developer.android.com/build/releases/agp-9-0-0-release-notes#android-gradle-plugin-built-in-kotlin) | `false` → `true` | [Migrate to built-in Kotlin](https://developer.android.com/build/migrate-to-built-in-kotlin) if you can or [opt out](https://developer.android.com/build/migrate-to-built-in-kotlin#opt-out-of-built-in-kotlin). | +| `android.uniquePackageNames` | Enforces that each library has a distinct package name. | `false` → `true` | Specify unique package names for all libraries within your project. If that is not possible, you can disable this flag while you migrate. | +| `android.useAndroidx` | Use [`androidx`](https://developer.android.com/jetpack/androidx) dependencies by default. | `false` → `true` | Adopt [`androidx`](https://developer.android.com/jetpack/androidx/migrate) dependencies. | +| `android.default.androidx.test.runner` | Run on-device tests with the [`androidx.test.runner.AndroidJUnitRunner`](https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/runner) class by default, replacing the default of the deprecated [`InstrumentationTestRunner`](https://developer.android.com/reference/android/test/InstrumentationTestRunner) for ``` android { defaultConfig { testInstrumentationRunner = "..." } } ``` | `false` → `true` | Adopt [`AndroidJUnitRunner`](https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/runner), or specify your custom `testInstrumentationRunner` explicitly. | +| `android.dependency.useConstraints` | Controls the use of dependency constraints between configurations. The default in AGP 9.0 is `false` which only uses constraints in application device tests (AndroidTest). Setting this to `true` will revert back to the 8.13 behavior. | `true` → `false` | Don't use dependency constraints everywhere unless you need them. Accepting the new default of this flag also enables optimizations in the project import process which should reduce the import time for builds with many Android library subprojects. | +| `android.enableAppCompileTimeRClass` | Compile code in applications against a non-final R class, bringing application compilation in line with library compilation. This improves incrementality and paves the way for future performance optimizations to the resource processing flow. | `false` → `true` | Many projects can just adopt the new behavior with no source changes. If the R class fields are used anywhere that requires a constant, such as switch cases, refactor to use chained if statements. | +| `android.sdk.defaultTargetSdkToCompileSdkIfUnset` | Uses the compile SDK version as the default value for the target SDK version in apps and tests. Before this change, the target SDK version would default to the min SDK version. | `false` → `true` | Specify the target SDK version explicitly for apps and tests. | +| `android.onlyEnableUnitTestForTheTestedBuildType` | Only creates unit test components for the tested build type. In the default project this results in a single unit test for debug, where the previous behavor was to have unit tests run for debug or release. | `false` → `true` | If your project doesn't require tests to run for both debug and release, no change is required. | +| `android.proguard.failOnMissingFiles` | Fails the build with an error if any of the keep files specified in the AGP DSL don't exist on disk. Before this change typos in filenames would result in files being silently ignored. | `false` → `true` | Remove any invalid proguard files declarations | +| `android.r8.optimizedResourceShrinking` | Allows R8 to keep fewer Android resources by considering classes and Android resources together. | `false` → `true` | If your project's keep rules are already complete, no change is required. | +| `android.r8.strictFullModeForKeepRules` | Allows R8 to keep less by not implicitly keeping the default constructor when a class is kept. That is, `-keep class A` no longer implies `-keep class A { (); }` | `false` → `true` | If your project's keep rules are already complete, no change is required.
Replace `-keep class A` with `-keep class A { (); }` in your project's keep rules for any cases where you need the default constructor to be kept. | +| `android.defaults.buildfeatures.resvalues` | Enables [`resValues`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/HasAndroidResources#resValues()) in all subprojects | `true` → `false` | Enable `resValues` in only the subprojects that need it by setting the following in those projects' Gradle build files: ``` android { buildFeatures { resValues = true } } ``` | +| `android.defaults.buildfeatures.shaders` | Enables [shader compilation](https://developer.android.com/ndk/guides/graphics/shader-compilers) in all subprojects | `true` → `false` | Enable shader compilation in only the subprojects that contain shaders to be compiled by setting the following in those projects' Gradle build files: ``` android { buildFeatures { shaders = true } } ``` | +| `android.r8.proguardAndroidTxt.disallowed` | In AGP 9.0, `getDefaultProguardFile()` will only support `proguard-android-optimize.txt` rather than `proguard-android.txt`. This is to prevent accidental usage of the `­dontoptimize` flag, which is included in `proguard-android.txt`. | `false` → `true` | You can explicitly specify `­dontoptimize` in a custom proguardFile if you want to avoid optimization, alongside using `proguard-android-optimize.txt`. Make sure to remove the `­dontoptimize` flag from this file if possible, as it reduces R8 optimization benefits. If not, opt out by setting `android.r8.globalOptionsInConsumerRules.disallowed=false`. | +| `android.r8.globalOptionsInConsumerRules.disallowed` | From AGP 9.0, Android library and feature module publishing will fail if consumer keep files contain problematic Proguard configurations. Consumer keep files that include global options like `­dontoptimize` or `­dontobfuscate` should only be used in application modules, and can reduce optimization benefits for library users. Android App module compilation will silently ignore any such global options if embedded in a pre-compiled dependency (JAR or AAR). You can see when this occurs by checking configuration.txt (typically in a path like `/build/outputs/mapping//configuration.txt`) for comments like: `# REMOVED CONSUMER RULE: ­dontoptimize` | `false` → `true` | Published libraries should remove any incompatible rules. Internal libraries should move any incompatible but required rules to a proguardFile in an app module instead. Opt out by setting `android.r8.globalOptionsInConsumerRules.disallowed=false`. Once all your consumer keep files are compatible, remove the opt out. | +| `android.sourceset.disallowProvider` | Disallow passing providers for generated sources using the [`AndroidSourceSet`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/AndroidSourceSet) DSL. | `false` → `true` | Use the [`Sources`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/Sources) API on `androidComponents` to register generated sources. | +| `android.custom.shader.path.required` | Requires the shader compiler path to be explicitly set in `local.properties` if shader compilation is enabled. | `false` → `true` | Add `glslc.dir=/path/to/shader-tools` to your project's `local.properties`. | + +## Removed features + +Android Gradle plugin 9.0 removes the following functionality: + +- **Embedded Wear OS app support** + AGP 9.0 removes support for embedding Wear OS apps, which is no longer supported in Play. This includes removing the `wearApp` configurations and the [`AndroidSourceSet.wearAppConfigurationName`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/AndroidSourceSet#wearAppConfigurationName()) DSL. See [Distribute to Wear OS](https://developer.android.com/distribute/best-practices/launch/distribute-wear) for how to publish your app to Wear OS. +- **`androidDependencies` and `sourceSets` report task** +- **Density split APK support** + AGP 9.0 removes support for creating split APKs based on screen density. The functionality and the related APIs have been removed. To split APKs based on screen density using AGP 9.0 or higher, use [app bundles](https://developer.android.com/guide/app-bundle). + +## Changed DSL + +Android Gradle plugin 9.0 has the following breaking DSL changes: + +- The parameterization of [`CommonExtension`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/CommonExtension) has been removed. + + In itself, this is only a source-level breaking change to help + avoid future source-level breaking changes, but it also means that + the block methods need to move from [`CommonExtension`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/CommonExtension) to + [`ApplicationExtension`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/ApplicationExtension), [`LibraryExtension`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/LibraryExtension), + [`DynamicFeatureExtension`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/DynamicFeatureExtension) and [`TestExtension`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/TestExtension). + + When upgrading your project to AGP 9.0, refactor Gradle plugin code + which uses those parameters or the block methods. For example the following + plugin is updated to remove the type parameter and not rely on the + removed block methods: + + **AGP 8.13** + + val commonExtension: CommonExtension<*, *, *, *, *, *> = + extensions.getByType(CommonExtension::class) + commonExtension.apply { + defaultConfig { + minSdk { + version = release(28) + } + } + } + + **AGP 9.0** + + val commonExtension: CommonExtension = + extensions.getByType(CommonExtension::class) + commonExtension.apply { + defaultConfig.apply { + minSdk { + version = release(28) + } + } + } + + For plugins which target a range of AGP versions, using the getter directly + is binary compatible with AGP versions lower than 9.0. + +## Removed DSL + +Android Gradle plugin 9.0 removes: + +- [`AndroidSourceSet.jni`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/AndroidSourceSet#jni(kotlin.Function1)), because it was not functional. + +- [`AndroidSourceSet.wearAppConfigurationName`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/AndroidSourceSet#wearAppConfigurationName()), as it relates to the + removed embedded Wear OS app support. + +- [`BuildType.isRenderscriptDebuggable`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/BuildType#isRenderscriptDebuggable()), because it was not functional. + +- [`DependencyVariantSelection`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/DependencyVariantSelection). It is replaced By + [`DependencySelection`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/DependencySelection), which is exposed as + [`kotlin.android.localDependencySelection`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/KotlinMultiplatformAndroidLibraryExtension#localDependencySelection(kotlin.Function1)) + +- [`Installation.installOptions(String)`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/Installation#installOptions(kotlin.String)). It is replaced by the + mutable property of [`Installation.installOptions`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/Installation#installOptions()). + +- The experimental, but never stabilized [`PostProcessing`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/PostProcessing) block. + +- [`ProductFlavor.setDimension`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/ProductFlavor#setDimension(kotlin.String)), which is replaced by the + [`dimension`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/dsl/ProductFlavor#dimension()) property + +- `LanguageSplitOptions`, which was only useful for + [Google Play Instant](https://developer.android.com/topic/google-play-instant), which is deprecated. + +- [`DensitySplit`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/dsl/DensitySplit), because the feature is no longer supported. + Replacement is to use [App Bundles](https://developer.android.com/guide/app-bundle). + +## Removed APIs + +Android Gradle plugin 9.0 removes: + +- [`AndroidComponentsExtension.finalizeDSl`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/AndroidComponentsExtension#finalizeDSl(org.gradle.api.Action)). It is replaced by + [`finalizeDsl`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/DslLifecycle#finalizeDsl(kotlin.Function1)) + +- [`Component.transformClassesWith`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/Component#transformClassesWith(java.lang.Class,com.android.build.api.instrumentation.InstrumentationScope,kotlin.Function1)). It is replaced by + [`Instrumentation.transformClassesWith`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/Instrumentation#transformClassesWith(java.lang.Class,com.android.build.api.instrumentation.InstrumentationScope,kotlin.Function1)) + +- [`Component.setAsmFramesComputationMode`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/Component#setAsmFramesComputationMode(com.android.build.api.instrumentation.FramesComputationMode)). It is replaced by + [`Instrumentation.setAsmFramesComputationMode`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/Instrumentation#setAsmFramesComputationMode(com.android.build.api.instrumentation.FramesComputationMode)) + +- [`ComponentBuilder.enabled`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/ComponentBuilder#enabled()). It is replaced by + [`ComponentBuilder.enable`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/ComponentBuilder#enable()). + +- [`DependenciesInfoBuilder.includedInApk`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/DependenciesInfoBuilder#includedInApk()). Is is replaced by + [`includeInApk`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/DependenciesInfoBuilder#includeInApk()) + +- [`DependenciesInfoBuilder.includedInBundle`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/DependenciesInfoBuilder#includedInBundle()). Is is replaced by + [`includeInBundle`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/DependenciesInfoBuilder#includeInBundle()) + +- [`GeneratesApk.targetSdkVersion`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/GeneratesApk#targetSdkVersion()). Is is replaced by [`targetSdk`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/GeneratesApk#targetSdk()) + +- [`Variant.minSdkVersion`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/Variant#minSdkVersion()). Is is replaced by [`minSdk`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/Variant#minSdk()) + +- [`Variant.maxSdkVersion`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/Variant#maxSdkVersion()). Is is replaced by [`maxSdk`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/Variant#maxSdk()) + +- [`Variant.targetSdkVersion`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/Variant#targetSdkVersion()). Is is replaced by [`targetSdk`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/Variant#targetSdk()) + +- [`Variant.unitTest`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/Variant#unitTest()), as it was not applicable to the + [`com.android.test`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/gradle/TestPlugin) plugin. + `unitTest` is available on [`VariantBuilder`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/VariantBuilder) subtypes extending + [`HasUnitTest`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/HasUnitTest). + +- [`VariantBuilder.targetSdk`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/VariantBuilder#targetSdk()) and [`targetSdkPreview`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/VariantBuilder#targetSdkPreview()), + as they were not meaningful in libraries. Use + [`GeneratesApkBuilder.targetSdk`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/GeneratesApkBuilder#targetSdk()) or + [`GeneratesApkBuilder.targetSdkPreview`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/GeneratesApkBuilder#targetSdkPreview()) + instead. + +- [`VariantBuilder.enableUnitTest`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/VariantBuilder#enableUnitTest()), as it was not applicable to the + [`com.android.test`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/gradle/TestPlugin) plugin. + `enableUnitTest` is available on [`VariantBuilder`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/VariantBuilder) subtypes extending + [`HasUnitTestBuilder`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/HasUnitTestBuilder). + +- [`VariantBuilder.unitTestEnabled`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/VariantBuilder#unitTestEnabled()) is removed in favor of the more + consistently + named [`enableUnitTest`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/HasUnitTestBuilder#enableUnitTest()) on the [`VariantBuilder`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/VariantBuilder) subtypes + extending [`HasUnitTestBuilder`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/HasUnitTestBuilder). + +- [`VariantOutput.enable`](https://developer.android.com/reference/tools/gradle-api/8.13/com/android/build/api/variant/VariantOutput#enable()). Is is replaced by [`enabled`](https://developer.android.com/reference/tools/gradle-api/9.0/com/android/build/api/variant/VariantOutput#enabled()) + +- The deprecated and disabled `FeaturePlugin` and `FeatureExtension`. + +- The deprecated and disabled `BaseExtension.registerTransform` APIs, which + only remained to allow compiling against the latest AGP version while + targeting running on AGP 4.2 or lower. + +## Removed Gradle properties + +The following Gradle properties were initially added as ways to globally +disable features that were enabled by default. + +These features have been disabled by default since AGP 8.0 or lower. Enable +these features in only the sub-projects that use them for a more efficient +build. + +| Property | Function | Replacement | +|---|---|---| +| `android.defaults.buildfeatures.aidl` | Enables [AIDL compilation](https://source.android.com/docs/core/architecture/aidl) in all subprojects | Enable AIDL compilation in only the subprojects where there are AIDL sources by setting the following property in those projects' Gradle build files: ``` android { buildFeatures { aidl = true } } ``` in the Gradle build file of each subproject containing AIDL sources | +| `android.defaults.buildfeatures.renderscript` | Enables [RenderScript compilation](https://source.android.com/docs/core/architecture/vndk/renderscript) in all subprojects | Enable renderscript compilation in only the subprojects where there are renderscript sources by setting the following property in those projects' Gradle build files: ``` android { buildFeatures { renderScript = true } } ``` | + +## Enforced Gradle properties + +AGP 9.0 throws an error if you set the following Gradle properties. + +The [Android Gradle plugin Upgrade Assistant](https://developer.android.com/build/agp-upgrade-assistant) won't upgrade projects to +AGP 9.0 that use these properties. + +| Property | Function | +|---|---| +| `android.r8.integratedResourceShrinking` | Resource shrinking is now always run as part of R8, the previous implementation has been removed. | +| `android.enableNewResourceShrinker.preciseShrinking` | Resource shrinking now always uses precise resource shrinking, which enables more to be removed. | + +## R8 changes + +The following R8 changes are included in AGP 9.0.0. + +### New configuration option `-processkotlinnullchecks` + +We've added the new R8 option `-processkotlinnullchecks` to configure R8 for +processing Kotlin null checks. The option takes a mandatory argument that must +be one of the following three values: `keep`, `remove_message` and `remove`. +The option processes the following null checks added by the Kotlin +compiler: + + class kotlin.jvm.internal.Intrinsics { + void checkNotNull(java.lang.Object); + void checkNotNull(java.lang.Object, java.lang.String); + void checkExpressionValueIsNotNull( + java.lang.Object, java.lang.String); + void checkNotNullExpressionValue( + java.lang.Object, java.lang.String); + void checkReturnedValueIsNotNull( + java.lang.Object, java.lang.String); + void checkReturnedValueIsNotNull( + java.lang.Object, java.lang.String, java.lang.String); + void checkFieldIsNotNull(java.lang.Object, java.lang.String); + void checkFieldIsNotNull( + java.lang.Object, java.lang.String, java.lang.String); + void checkParameterIsNotNull(java.lang.Object, java.lang.String); + void checkNotNullParameter(java.lang.Object, java.lang.String); + } + +The option values, ordered from the weakest to the strongest, have the +following effect: + +- `keep` doesn't change the checks. +- `remove_message` rewrites each check method call to a call to `getClass()` on the first argument of the call (effectively keeping the null check, but without any message). +- `remove` completely removes the checks. + +By default R8 +uses `remove_message`. Any specification of `-processkotlinnullchecks` +will override that. If specified multiple times the strongest value is +used. + +### Stop propagating keep info to companion methods + +When keep rules match interface methods that are subject to desugaring, +R8 previously internally transferred the *disallow optimization* and *disallow +shrinking* bits to the synthesized companion methods. + +Starting with AGP 9.0, keep rules no longer apply to +companion methods. This is consistent with the fact that keep rules are +not applicable to other compiler synthesized fields/methods/classes. + +By transferring the *disallow optimization* and *disallow shrinking* bits to +the companion methods, the following use case was previously supported: + +1. Compile a library with `default`/`static`/`private` interface methods to DEX with `minSdk` \< 24 and rules that keep the interface methods. +2. Compile an app with the library on classpath and `-applymapping`. +3. Merge the app and the library. + +Note that this only works with `-applymapping` since the `disallow +obfuscation` bit is not transferred to the companion methods---that is, the +companion classes generated from step 1 would have obfuscated method +names. + +Going forward this use case is no longer supported for `minSdk` \< +24. The workaround is to do the following: + +1. Desugar the library with `default`/`static`/`private` interface methods to class files with `minSdk` \< 24. +2. Compile the desugared artifact using R8 and rules that keep the interface methods on the companion classes. +3. Compile the app with the library on classpath. +4. Merge the app and the desugared artifact. + +Another side effect of this is that it is no longer possible to keep the +inner class and enclosing method attributes for anonymous and local classes +inside interface companion methods. + +### Change the default emitted source file to `r8-map-id-` + +This change is in AGP starting from 8.12.0. + +The default emitted source file attribute for a class changes from +`SourceFile` to `r8-map-id-` when retracing is required (that is, +when either obfuscation or optimization is enabled). + +Given an obfuscated stack trace, the new source file attribute makes it +possible to extract the ID of the mapping file that is required for +retracing, which can be used to support +[automated retracing of stack traces in Logcat](https://developer.android.com/studio/preview/features#logcat-retrace). + +If a custom source file attribute is used (`-renamesourcefileattribute`) +this custom source file attribute continues to take precedence. + +In ProGuard compatibility mode (when `gradle.properties` contains +`android.enableR8.fullMode=false`), emitting a source file attribute of +`r8-map-id-` only takes effect if the `SourceFile` +attribute is *not* kept. Apps that use ProGuard compatibility mode and want +to include the mapping file ID in their stack traces should remove +`-keepattributes SourceFile` (or migrate to R8 full mode). + +The map ID used in `r8-map-id-` is the full map hash, and not a 7 +character prefix of the map hash which was previously used. + +### Enable use of minimized synthetic names in L8 desugaring + +The name of synthetic classes generated by D8 normally contains the substring +`$$ExternalSynthetic` that tells you that this is a synthetic generated by D8. +Moreover, the name of the synthetic also encodes the synthetic kind (for +example, `Backport`, `Lambda`). This has a negative impact on the resulting DEX +size, since the class names take up more space in the string pool. + +AGP 9.0 configures L8 (core library desugaring) so that the DEX +file containing all `j$` classes uses a new shortened class name format +for synthetic classes. The new class name uses a numeric ID (for example, `$1`). + +### Remove support for `-addconfigurationdebugging` + +AGP 9.0 removes support for `-addconfigurationdebugging`. The compiler now +reports a warning if the flag is used. + +### Remove support for generating L8 rules from D8/R8 + +This change is only relevant for developers using the D8/R8 command line or +APIs directly. + +R8 9.0 removes support for generating keep rules for L8 from D8 and R8. +You should instead use `TraceReferences` for this purpose. + +More specifically, the methods +`D8Command.builder.setDesugaredLibraryKeepRuleConsumer` and +`R8Command.Builder.setDesugaredLibraryKeepRuleConsumer` are removed, and the +support for `--desugared-lib-pg-conf-output` is removed from the command line +options of D8 and R8. + +## Fixed issues + +### Android Gradle plugin 9.0.0 + +| Fixed Issues || +|---|---| +| **Android Gradle Plugin** | |---| | [Issue #171293712](https://issuetracker.google.com/issues/171293712) Feature Request: Inject ideal AGP version as a property | | [Issue #443976533](https://issuetracker.google.com/issues/443976533) Stabilize SingleArtifact.VERSION_CONTROL_INFO_FILE | | [Issue #223643506](https://issuetracker.google.com/issues/223643506) androidTest connectedCheck logcat output is broken | | [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets | | [Issue #460094802](https://issuetracker.google.com/issues/460094802) missingDimensionStrategy prefers a flavor maching its own name even from an unrelated dimension | | [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets | | [Issue #471410336](https://issuetracker.google.com/issues/471410336) AGP 9.0.0-rc01 doesn't resolve Kotlin libraries via kotlin() function | | [Issue #450851465](https://issuetracker.google.com/issues/450851465) Built-in-kotlin does not publish kotlin-stdlib dependency constraint in maven pom | | [Issue #420592288](https://issuetracker.google.com/issues/420592288) Add a test case for divergence between compileSdk and targetSdk | | [Issue #449153004](https://issuetracker.google.com/issues/449153004) empty resConfigs value leads to obscure aapt error | | [Issue #447375921](https://issuetracker.google.com/issues/447375921) Pure Java project have dependency on kotlin stdlib. | | [Issue #368600704](https://issuetracker.google.com/issues/368600704) Remove deprecated KotlinMultiplatformAndroidCompilationBuilder properties in AGP 9.0 | | [Issue #445209309](https://issuetracker.google.com/issues/445209309) \`com.android.tools.build:gradle:9.0.0-alpha05\` should have an api dependency on KGP and gradle-api | | [Issue #452645779](https://issuetracker.google.com/issues/452645779) Rename com.android.experimental.built-in-kotlin Gradle plugin | | [Issue #446220448](https://issuetracker.google.com/issues/446220448) AGP9: \`variant.sources.kotlin!!.addGeneratedSourceDirectory()\` is not working | | [Issue #448450771](https://issuetracker.google.com/issues/448450771) Aar metadata checks on compile Sdk uses the old DSL | | [Issue #441523448](https://issuetracker.google.com/issues/441523448) Remove deprecated \`com.android.build.api.dsl.ManagedDevices.devices\` property | | [Issue #386221070](https://issuetracker.google.com/issues/386221070) Built-in Kotlin support in AGP should not synchronize with the Kotlin sourcesets | | [Issue #433758231](https://issuetracker.google.com/issues/433758231) Fail android library publishing if consumer keep file contains -dontobfuscate | | [Issue #241955408](https://issuetracker.google.com/issues/241955408) No Options to Print Mapping for Optimized Resources | | [Issue #436595826](https://issuetracker.google.com/issues/436595826) Make it an error to call finalizeDsl after this phase has been passed | | [Issue #457089670](https://issuetracker.google.com/issues/457089670) AGP initializes jetifier config even when jetifier is disabled | | [Issue #452246814](https://issuetracker.google.com/issues/452246814) built in kotlin does not add kotlinStdlib as a compile time dependency when \`kotlin.stdlib.default.dependency\` is true to module and pom files | | [Issue #442250902](https://issuetracker.google.com/issues/442250902) New optimizaation DSL does not create configuration.txt by default | | [Issue #443587266](https://issuetracker.google.com/issues/443587266) AGP 8.13.0 fails to verify nav graph in a module | | [Issue #444260628](https://issuetracker.google.com/issues/444260628) AGP uses deprecated Gradle API: multi-string notation | | [Issue #347732357](https://issuetracker.google.com/issues/347732357) Warn users trying to use legacy multidex library with minSdkVersion \>=21 | | [Issue #333831734](https://issuetracker.google.com/issues/333831734) A build fails if there are code generation tasks | | [Issue #446123111](https://issuetracker.google.com/issues/446123111) With \`android.builtInKotlin=false\` and \`android.newDsl=false\` and \`android.enableLegacyVariantApi=false\`, using \`kotlin-android\` plugin will fail with "API 'applicationVariants' is obsolete" | | [Issue #443037365](https://issuetracker.google.com/issues/443037365) Built-in Kotlin fails to resolve unversioned kotlin-stdlib when kotlin.stdlib.default.dependency=false | | [Issue #445967244](https://issuetracker.google.com/issues/445967244) DexData opens a file without closing, preventing cleanup | | [Issue #368609737](https://issuetracker.google.com/issues/368609737) AndroidSourceDirectorySet should stop extending PatternFilterable in AGP 9.0 | | [Issue #389707041](https://issuetracker.google.com/issues/389707041) Test Fixture Error in test only modules | | [Issue #353249347](https://issuetracker.google.com/issues/353249347) Incorrect error when using context receivers in test fixtures | | [Issue #351046197](https://issuetracker.google.com/issues/351046197) Incorrect IDE errors for Kotlin code in testFixtures | | [Issue #446889652](https://issuetracker.google.com/issues/446889652) \`legacy-kapt\` plugin skips annotation processing unlike \`kotlin-kapt\` | | [Issue #446492061](https://issuetracker.google.com/issues/446492061) compileSdkSpec.minorApiLevel is not working with SettingsExtension | | [Issue #429253579](https://issuetracker.google.com/issues/429253579) \[fused lib - public\] Generated fused library does not include sources | | [Issue #149770867](https://issuetracker.google.com/issues/149770867) extractNativeLibs and useEmbeddedDex should not be coming from the manifest | | [Issue #449114518](https://issuetracker.google.com/issues/449114518) Warnings from R8 in AGP 9.0.0-alpha09 | | [Issue #368426598](https://issuetracker.google.com/issues/368426598) Remove deprecated AndroidSourceSet.jni in AGP 9.0 | | [Issue #368484483](https://issuetracker.google.com/issues/368484483) Remove Installation.installOptions() in AGP 9.0 | | [Issue #368482484](https://issuetracker.google.com/issues/368482484) Remove BuildType.isRenderscriptDebuggable in AGP 9.0. | | [Issue #428646179](https://issuetracker.google.com/issues/428646179) Remove android.defaults.buildfeatures.renderscript | | [Issue #436887358](https://issuetracker.google.com/issues/436887358) \`com.android.kotlin.multiplatform.library\` crashes with Gradle Managed Devices | | [Issue #428645763](https://issuetracker.google.com/issues/428645763) Remove \`android.defaults.buildfeatures.aidl\` defaults gradle.properties flags | | [Issue #294183018](https://issuetracker.google.com/issues/294183018) Fail build when proguard file does not exist | | [Issue #254305041](https://issuetracker.google.com/issues/254305041) remove buildconfig defaults gradle.properties flags | | [Issue #280674230](https://issuetracker.google.com/issues/280674230) Change the app's targetSdk default value to be based on compileSdk instead of minSdk | | [Issue #436878535](https://issuetracker.google.com/issues/436878535) When \`isIncludeAndroidResources\` is enabled, \`process{Variant}UnitTestManifest\` fails to merge tools:overrideLibrary usages in AGP 8.12.0 | | [Issue #411739086](https://issuetracker.google.com/issues/411739086) AGP causing deprecation warnings in Gradle for JVM test tasks | | [Issue #235457021](https://issuetracker.google.com/issues/235457021) DependencyReportTask is incompatible with the configuration cache | | [Issue #369246556](https://issuetracker.google.com/issues/369246556) Switch default source/target Java version from Java 8 to Java 11 in AGP 9.0 | | [Issue #258855275](https://issuetracker.google.com/issues/258855275) Flip android.useAndroidX default to true | | [Issue #442763200](https://issuetracker.google.com/issues/442763200) Better exception when applying kapt plugin with built-in Kotlin. | | [Issue #441679226](https://issuetracker.google.com/issues/441679226) android.proguard.failOnMissingFiles is not working for consumerProguardFiles | | [Issue #443051391](https://issuetracker.google.com/issues/443051391) Update Kotlin Gradle plugin dependency to 2.2.10 | | [Issue #429981132](https://issuetracker.google.com/issues/429981132) Create KotlinJvmAndroidCompilation using KGP API | | [Issue #442869731](https://issuetracker.google.com/issues/442869731) Kotlin explicit API mode applied to test sources | | +| **Lint** | |---| | [Issue #430991549](https://issuetracker.google.com/issues/430991549) AGP 8.11.0: lintAnalyzeRelease task crashes when applying .gradle.kts files with apply(from = "...") | | [Issue #441536820](https://issuetracker.google.com/issues/441536820) Lint ChecksSdkIntAtLeast Check does not check if the annotated value is correct | | [Issue #446696613](https://issuetracker.google.com/issues/446696613) Built-in Kotlin does not add .kotlin_module to META-INF | | [Issue #449031505](https://issuetracker.google.com/issues/449031505) Lint classpath contains duplicate classes at different versions | | [Issue #448148350](https://issuetracker.google.com/issues/448148350) Overriding private resources workaround not working (tools:override = "true") | | [Issue #405676712](https://issuetracker.google.com/issues/405676712) Bug: removal of unused resources doesn't also remove the translations of them, and doesn't ask about it either | | [Issue #440415636](https://issuetracker.google.com/issues/440415636) Lint throwing warning "Could not clean up K2 caches" | | [Issue #440415636](https://issuetracker.google.com/issues/440415636) Lint throwing warning "Could not clean up K2 caches" | | +| **Lint Integration** | |---| | [Issue #460068798](https://issuetracker.google.com/issues/460068798) AndroidLintAnalysisTask cache misses across different JDK vendors or minor versions due to systemPropertyInputs.javaVersion differences | | [Issue #444447002](https://issuetracker.google.com/issues/444447002) Lint automatically uses latest installed SDK despite compileSdk, doesn't register as task input and breaks caching | | +| **Shrinker (R8)** | |---| | [Issue #454927488](https://issuetracker.google.com/issues/454927488) R8 optimized resource shrinking silently fails if using final resource IDs | | + +
\ No newline at end of file diff --git a/build/agp/agp-9-upgrade/references/buildconfig.md b/build/agp/agp-9-upgrade/references/buildconfig.md new file mode 100644 index 0000000..273f876 --- /dev/null +++ b/build/agp/agp-9-upgrade/references/buildconfig.md @@ -0,0 +1,54 @@ +When an Android module contains custom BuildConfig fields, the following steps +are necessary to ensure a correct build. + +### Step 1: Enable the buildConfig build feature + +In a build script: + + android { + buildFeatures { + buildConfig = true + } + } + +In custom build-logic for an app module: + + extensions.configure { + buildFeatures { + buildConfig = true + } + } + +In custom build-logic for a library module: + + extensions.configure { + buildFeatures { + buildConfig = true + } + } + +In custom build-logic using `CommonExtension`: + + extensions.configure { + buildFeatures { + buildConfig = true + } + } + +### Step 2: Migrate to the new API + +Use the **addCustomBuildConfigFields** recipe from the [gradle-recipes](https://developer.android.com/agents/skills/build/agp/agp-9-upgrade/references/recipes) +repository. + +**IMPORTANT:** For `BuildConfigField`s with a type of `String`, the `value` field +*must* include quotation marks as part of the String. For example: + + BuildConfigField( + type = "String", + value = "\"Some value\"", + comment = "Optional comment", + ) + +It is an **error** if the `value` field doesn't include quotation marks as +part of the String. For example, `value = "Some value"` **is an error** . This is +because the `value` is written out literally. \ No newline at end of file diff --git a/build/agp/agp-9-upgrade/references/ksp-kapt.md b/build/agp/agp-9-upgrade/references/ksp-kapt.md new file mode 100644 index 0000000..e8a5d33 --- /dev/null +++ b/build/agp/agp-9-upgrade/references/ksp-kapt.md @@ -0,0 +1,39 @@ +When migrating to built-in Kotlin, it is important to consider usage of `kapt` +and the `org.jetbrains.kotlin.kapt` (also known as the `kotlin("kapt")`) plugin. +The goal is to migrate as many `kapt` usages to `ksp` as possible. + +Follow these steps when migrating `kapt`: + +## 1. Remove all references to the `org.jetbrains.kotlin.kapt` plugin + +The `org.jetbrains.kotlin.kapt` (also known as `kotlin("kapt")`) plugin is +incompatible with built-in Kotlin. Remove it when migrating to built-in Kotlin. + +## 2. Check each usage of `kapt` + +Check each usage of `kapt` to see if it is compatible with `ksp`. To check if a +dependency is compatible with `ksp`, inspect the dependency's jar. For it to be +compatible with `ksp`, the jar must have a file, +`services/com.google.devtools.ksp.processing.SymbolProcessorProvider`. If it +does not, it is **incompatible** with `ksp`. + +For example, the `androidx.room:room-compiler` library is compatible with KSP +since version 2.3.0-beta02. We can verify this by finding the jar file in the +Gradle caches directory, which is typically located at +`~/.gradle/caches/modules-2/files-2.1/` on Linux and Mac. In this specific case, +the `androidx.room:room-compiler` dependency is located at +`~/.gradle/caches/modules-2/files-2.1/androidx.room/room-compiler/`. + +More generally, you can find a dependency by looking in +`~/.gradle/caches/modules-2/files-2.1/group-name/artifact-name/`. + +## 3. Migrate to KSP where possible + +For each usage of `kapt` that is compatible with `ksp`, use `ksp`. The prior +step explains how to check compatibility. + +## 4. Apply legacy-kapt + +If a Gradle module has `kapt` dependencies that cannot be migrated to `ksp` +because they are incompatible (see step 2), then leave that dependency alone and +apply the `com.android.legacy-kapt` plugin. \ No newline at end of file diff --git a/build/agp/agp-9-upgrade/references/paparazzi-gradle-9.md b/build/agp/agp-9-upgrade/references/paparazzi-gradle-9.md new file mode 100644 index 0000000..a20da2a --- /dev/null +++ b/build/agp/agp-9-upgrade/references/paparazzi-gradle-9.md @@ -0,0 +1,30 @@ +If Paparazzi is used in the project, update it to version 2.0.0-alpha04 or +higher. + +Paparazzi version 2.0.0-alpha04 and lower is not fully compatible with Gradle +9, and Gradle 9 is required by AGP 9. This means that, without workarounds, +projects that use Paparazzi v2.0.0-alpha04 and lower cannot migrate to AGP 9. + +At time of writing, there are no higher versions of Paparazzi. That is, +v2.0.0-alpha04 is the latest release. + +The issue is due to Paparazzi using internal classes from Gradle that tend to +move in breaking ways without warning. This specific issue is related to HTML +test reports. To work around it, disable those HTML test reports. Here +are two examples of how to do this, one for Kotlin DSL and the other for Groovy +DSL. Any module that has the paparazzi plugin (`app.cash.paparazzi`) applied +must apply one of these two workarounds. + +Kotlin DSL: + + tasks.withType().configureEach { + // https://github.com/cashapp/paparazzi/issues/2111 + reports.html.required = false + } + +Groovy DSL: + + tasks.withType(Test).configureEach { + // https://github.com/cashapp/paparazzi/issues/2111 + reports.html.required = false + } \ No newline at end of file diff --git a/build/agp/agp-9-upgrade/references/recipes.md b/build/agp/agp-9-upgrade/references/recipes.md new file mode 100644 index 0000000..e6aec57 --- /dev/null +++ b/build/agp/agp-9-upgrade/references/recipes.md @@ -0,0 +1,59 @@ +When migrating to AGP's new DSL, any Gradle code (plugins or logic in build +scripts) that relied on the old DSL will stop working. Such code must be +migrated. + +In some cases, there is a one-to-one replacement for the old code. Some examples +are in [the AGP 9.0.0 release notes](https://developer.android.com/build/releases/agp-9-0-0-release-notes). + +In other cases, there is no direct one-to-one replacement. For these situations, +the [gradle-recipes repo](https://github.com/android/gradle-recipes) is a great resource. You can checkout one of its +AGP 9.x branches, such as `agp-9.0`, `agp-9.1`, or `agp-9.2`. These branches +contain recipes for common situations in Android projects. The following table +lists the compatibility for recipes for each version of AGP. + +## Compatibility table + +| AGP version | gradle-recipes branch | +|---|---| +| 9.0.x | agp-9.0 | +| 9.1.x | agp-9.1 | +| 9.2.x | agp-9.2 | + +## Recipes and use-cases + +The following table links use-cases to recipes. + +| Recipe | Use-case | +|---|---| +| addCustomBuildConfigFields | Add custom BuildConfig fields | +| listenToArtifacts | Rename APK | + +Additional details for each use-case follow. + +### Add custom BuildConfig fields + +See the detailed guide at [BuildConfig](https://developer.android.com/agents/skills/build/agp/agp-9-upgrade/references/buildconfig). + +### Renaming an APK + +In the old DSL, an APK could be renamed very simply. Here's an example: + + android { + applicationVariants.all { + outputs.all { + val output = this as com.android.build.gradle.api.ApkVariantOutput + val fileName = output.outputFileName + if (fileName.contains("release")) { + output.outputFileName = "my-cool-new-name.apk" + } + } + } + } + +However, with AGP 9 and the new DSL, `applicationVariants` is no longer +available. You must instead react to artifact creation using the +`androidComponents.onVariants` API. A complete example of this is available in +the **gradle-recipes** repository in the `listenToArtifacts` recipe. + +**DO NOT** search the web for examples of how to do this. Use the +**gradle-recipes** repository examples **only**. \ No newline at end of file diff --git a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/SKILL.md b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/SKILL.md index df788ab..dda754a 100644 --- a/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/SKILL.md +++ b/jetpack-compose/migration/migrate-xml-views-to-jetpack-compose/SKILL.md @@ -1,9 +1,15 @@ --- name: migrate-xml-views-to-jetpack-compose -description: Provides a structured workflow for migrating Android XML Views to Jetpack Compose. This skill details the step-by-step process, from planning and dependency setup to attribute conversion, style migration, and validation. Use this skill when you need to incrementally migrate XML Views to Jetpack Compose in a project. It solves the problem of converting legacy UI code into modern, declarative Compose components while maintaining interoperability. +description: Provides a structured workflow for migrating Android XML Views to Jetpack + Compose. This skill details the step-by-step process, from planning and dependency + setup to attribute conversion, style migration, and validation. Use this skill when + you need to incrementally migrate XML Views to Jetpack Compose in a project. It + solves the problem of converting legacy UI code into modern, declarative Compose + components while maintaining interoperability. +license: Complete terms in LICENSE.txt metadata: + author: Google LLC keywords: - - skill - Jetpack Compose - migration - XML @@ -13,59 +19,105 @@ metadata: - UI development --- -# Migrate XML Views to Jetpack Compose +This skill guides through the process of migrating an existing Android XML View +to Jetpack Compose. It performs a stable, safe and visually consistent +transition by following a structured, 10-step methodology. This skill migrates +UI (XML to Jetpack Compose) only. -Jetpack Compose supports interoperability with Views --- you -can use Compose in Views, and Views in Compose. This allows adoption of -Compose in existing View-based apps without having to migrate all Views -immediately. +## Objective -## Migration steps +To systematically convert a single legacy XML layout into modern, declarative +Jetpack Compose UI while maintaining pixel-perfect visual parity and functional +integrity. -1. **Create a plan:** Create a robust and step-by-step plan for performing the migration. We recommend a prioritized backlog of migration tasks. -2. **Identify the XML candidate for migration :** Identify and start from the smallest components that are leaf nodes in the hierarchy, and expand the migration plan from the bottom up to progressively higher components in the hierarchy. Good candidates for initial migration are small, stateless, and have fewer dependencies. -3. **Analyze the hierarchy:** Once you identify the XML View to migrate, analyze its XML layout structure and implementation. -4. **Capture the initial state:** Run a screenshot test to capture the initial state of the selected XML View. -5. **Prerequisite: Set up Compose dependencies** Identify if the project has Compose dependencies and Compiler set up. If it doesn't, follow [Setup Compose dependencies and Compiler](references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md.txt). -6. **Prerequisite: Set up Compose theming** Identify if the project has Compose theming setup already. If it doesn't, follow ompose theming. Keep the original XML theming while the app is interop [Migrate XML Theme to Compose](references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md.txt) to understand patterns of how to state and until the project is fully migrated to Compose. -7. **Migrate the XML View to Compose:** Start the conversion of the XML code to Compose, apply the appropriate theming, and add Compose Previews for migrated composables. For common migration scenarios, refer to additional resources. For example, for migrating to Lazy APIs in Compose, follow the steps in [Migrate RecyclerView to Compose](references/android/develop/ui/compose/migrate/migration-scenarios/recycler-view.md.txt). -8. **Replace usages:** Replace the previous usages of the XML View to use the new Compose component. To add Compose in Views, follow the steps in [Compose in Views](references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md.txt). To add Views in Compose, follow the steps in [Views in Compose](references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md.txt). -9. **Validate the migration:** Verify that the initial state captured in the screenshot test is same as the Compose Preview of the migrated composable. If they don't match, iterate on the new composable UI and improve it to align it with the initial state. Create new Compose UI tests for the new composable. -10. **XML removal:** Once the newly migrated composable is matching the initial XML UI, remove the obsolete XML View code and its tests. +## Summary of the 10-step migration process -## Common migration scenarios +1. **Identify the optimal XML candidate for migration** +2. **Analyze the project and layout** +3. **Create a plan** +4. **Capture the XML View UI** +5. **Set up Compose dependencies and compiler** +6. **Set up Compose theming** +7. **Migrate the XML layout to Compose** +8. **Validate the migration** +9. **Replace usages** +10. **XML code removal** -Verify `dp` and `sp` extensions are used (`16.dp`, `20.sp`) in composables. -If `tools:text` is present in the XML View, use it in a separate `@Preview` -composable. +## Detailed steps -### Attribute to Modifier conversion +### Step 1: Identify the optimal XML candidate for migration -Most XML attributes become part of the `modifier` chain or parameters of the -composable function. +If the user has explicitly specified a target XML layout, proceed to Step 2. +Otherwise, analyze the codebase to identify the best candidate for migration by +following the logic in [references/identify-optimal-xml-candidate.md](references/identify-optimal-xml-candidate.md). -| XML Attribute | Compose Equivalent | -|---|---| -| `android:layout_width="match_parent"` | `Modifier.fillMaxWidth()` | -| `android:layout_height="match_parent"` | `Modifier.fillMaxHeight()` | -| `android:layout_width="wrap_content"` | (Default behavior, usually no modifier needed) | -| `android:padding="Xdp"` | `Modifier.padding(X.dp)` | -| `android:layout_margin="Xdp"` | `Modifier.padding(X.dp)` (Outer padding) | -| `android:gravity="center"` | `contentAlignment = Alignment.Center` (Box) or `horizontalAlignment` / `verticalArrangement` (Column/Row) | -| `android:background="@color/white"` | `Modifier.background(colorResource(R.color.white))` | -| `android:visibility="gone"` | Wrap in `if (visible) { ... }` block | +### Step 2: Analyze the project and layout -## Migrate styles (styles.xml) +Analyze the identified XML View's structure, hierarchy, and implementation +details. +Use [references/analysis-of-the-project-and-layout.md](references/analysis-of-the-project-and-layout.md) to +guide your technical audit of the layout and surrounding project context. -XML styles often combine multiple attributes to create a style. In Compose, -this is done by creating a **composable** variation with a specific style. +### Step 3: Create a plan -Provide separate @Composable functions named according to the style and the -base component, -to signify the difference in styling and use cases for those components. +Using the outputs and analysis done in the Step 1 and 2, generate a +step-by-step plan for the migration. If you support user interaction, present +to the user and ask for approval before proceeding. If user interaction is not +supported, proceed to Step 4 following the generated plan. -- **Pattern:** If an XML element uses a custom style (e.g., `style="@style/MyPrimaryButton"`), don't try to replicate the style inline. Instead, suggest creating a specific composable. -- **Example:** - - *XML:* ` - -
- -If you run into problems [file an issue here](https://issuetracker.google.com/issues/new?component=1750212&template=2102223&title=%5BMigration%5D). - -## Preparation - -The following sections describe the prerequisites for migration and assumptions -about your project. They also cover the features that are supported for -migration, and those that aren't. - -### Prerequisites - -- You must use a `compileSdk` of 36 or later. -- You should be familiar with [navigation terminology](references/android/guide/navigation/index.md.txt). -- Destinations are composable functions. Navigation 3 is designed exclusively for Compose. To use Fragments and Views in Compose, see [Using Views in - Compose](references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md.txt). -- Routes are strongly typed. If you use string-based routes, [migrate to - type-safe routes](https://medium.com/androiddevelopers/type-safe-navigation-for-compose-105325a97657) first ([example](https://github.com/android/nowinandroid/pull/1413)). -- *Optional (but highly recommended)*: Test coverage that verifies existing navigation behavior. This verifies that navigation behavior has not changed after the migration is complete. - -### Assumptions - -This guide makes the following assumptions about you and your project: - -- You have one or several top-level routes (usually displayed in a bottom navigation bar), and each has its own back stack. -- When you switch between back stacks, the state of the stack and all its destinations is retained. -- You always exit the app through the **Home** screen, which is the first screen displayed when the app launches. -- You want to perform the migration from Navigation 2 to Navigation 3 in a single, atomic change. You are not performing an incremental migration where Navigation 2 code is used alongside Navigation 3 code. - -**AI Agent**: Before changing any code, check if these assumptions about the -user's project are true. If not, stop the migration and ask the user how to -proceed. - -### Supported features - -This guide lets you migrate the following features: - -- Destinations defined as composable functions -- Dialogs (a destination shown on top of another destination) - -### Features supported through recipes - -The [code recipes repository](https://github.com/android/nav3-recipes) demonstrates more nuanced use -cases, including: - -- [Bottom sheets](https://github.com/android/nav3-recipes/tree/main/app/src/main/java/com/example/nav3recipes/bottomsheet) (instructions are provided in this guide) -- [Modularized navigation code and injected destinations](https://github.com/android/nav3-recipes/tree/main/app/src/main/java/com/example/nav3recipes/modular/hilt) -- [Using and passing arguments to `ViewModel`](https://github.com/android/nav3-recipes?tab=readme-ov-file#passing-navigation-arguments-to-viewmodels) -- [Returning results from a screen](https://github.com/android/nav3-recipes?tab=readme-ov-file#returning-results) - -If your project has any of these features, check the relevant recipe to -understand how to implement them. - -**AI Agent**: Before changing any code, check if the project contains any -features supported through recipes. If it does, check the recipe's README and -source code. Create a migration plan based on the recipe. Do not proceed without -confirming the plan with the user. - -### Unsupported features - -This migration guide and the code recipes don't yet support the following -features. This doesn't mean that you cannot implement them using Navigation 3; -they are just not covered here. - -- More than one level of nested navigation -- Shared destinations: screens that can move between different back stacks -- [Custom destination types](references/android/guide/navigation/design/kotlin-dsl.md.txt) -- Deep links - -**AI Agent**: Before changing any code, check if the project contains any of the -unsupported features. If it does, do not proceed. Inform the user of the -unsupported feature and ask for further instructions. - -## Step 1: Add Navigation 3 dependencies - -Use the [Get started](references/android/guide/navigation/navigation-3/get-started.md.txt) page to add the Navigation 3 dependencies to your -project. The core dependencies are provided for you to copy. - -**lib.versions.toml** - - [versions] - nav3Core = "1.0.0" - - # If your screens depend on ViewModels, add the Nav3 Lifecycle ViewModel add-on library - lifecycleViewmodelNav3 = "2.10.0-rc01" - - [libraries] - # Core Navigation 3 libraries - androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } - androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } - - # Add-on libraries (only add if you need them) - androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } - -**app/build.gradle.kts** - - dependencies { - implementation(libs.androidx.navigation3.ui) - implementation(libs.androidx.navigation3.runtime) - - // If using the ViewModel add-on library - implementation(libs.androidx.lifecycle.viewmodel.navigation3) - } - -Also update the project's `minSdk` to 23 and the `compileSdk` to 36. You usually -find these in `app/build.gradle.kts` or `lib.versions.toml`. - -## Step 2: Update navigation routes to implement the `NavKey` interface - -Update every navigation [route](references/android/guide/navigation/index.md.txt) so that it implements the `NavKey` -interface. This lets you use `rememberNavBackStack` to assist with [saving your -navigation state](references/android/guide/navigation/navigation-3/save-state.md.txt). - -Before: - - @Serializable data object RouteA - -After: - - @Serializable data object RouteA : NavKey - -> [!NOTE] -> **Note:** The `@Serializable` annotation is provided by the KotlinX Serialization plugin. You can add this by following [these project setup steps](references/android/guide/navigation/navigation-3/get-started.md.txt). - -## Step 3: Create classes to hold and modify your navigation state - -### Step 3.1: Create a navigation state holder - -Copy the following code into a file named `NavigationState.kt`. Add your package -name to match your project structure. - - // package com.example.project - - import androidx.compose.runtime.Composable - import androidx.compose.runtime.MutableState - import androidx.compose.runtime.getValue - import androidx.compose.runtime.mutableStateOf - import androidx.compose.runtime.remember - import androidx.compose.runtime.saveable.rememberSerializable - import androidx.compose.runtime.setValue - import androidx.compose.runtime.snapshots.SnapshotStateList - import androidx.compose.runtime.toMutableStateList - import androidx.navigation3.runtime.NavBackStack - import androidx.navigation3.runtime.NavEntry - import androidx.navigation3.runtime.NavKey - import androidx.navigation3.runtime.rememberDecoratedNavEntries - import androidx.navigation3.runtime.rememberNavBackStack - import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator - import androidx.navigation3.runtime.serialization.NavKeySerializer - import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer - - /** - * Create a navigation state that persists config changes and process death. - */ - @Composable - fun rememberNavigationState( - startRoute: NavKey, - topLevelRoutes: Set - ): NavigationState { - - val topLevelRoute = rememberSerializable( - startRoute, topLevelRoutes, - serializer = MutableStateSerializer(NavKeySerializer()) - ) { - mutableStateOf(startRoute) - } - - val backStacks = topLevelRoutes.associateWith { key -> rememberNavBackStack(key) } - - return remember(startRoute, topLevelRoutes) { - NavigationState( - startRoute = startRoute, - topLevelRoute = topLevelRoute, - backStacks = backStacks - ) - } - } - - /** - * State holder for navigation state. - * - * @param startRoute - the start route. The user will exit the app through this route. - * @param topLevelRoute - the current top level route - * @param backStacks - the back stacks for each top level route - */ - class NavigationState( - val startRoute: NavKey, - topLevelRoute: MutableState, - val backStacks: Map> - ) { - var topLevelRoute: NavKey by topLevelRoute - val stacksInUse: List - get() = if (topLevelRoute == startRoute) { - listOf(startRoute) - } else { - listOf(startRoute, topLevelRoute) - } - } - - /** - * Convert NavigationState into NavEntries. - */ - @Composable - fun NavigationState.toEntries( - entryProvider: (NavKey) -> NavEntry - ): SnapshotStateList> { - - val decoratedEntries = backStacks.mapValues { (_, stack) -> - val decorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - ) - rememberDecoratedNavEntries( - backStack = stack, - entryDecorators = decorators, - entryProvider = entryProvider - ) - } - - return stacksInUse - .flatMap { decoratedEntries[it] ?: emptyList() } - .toMutableStateList() - } - -**AI Agent** : `rememberSerializable` is correct. Do not change it to -`rememberSaveable`. - -This file contains a state holder class named `NavigationState` and associated -helper functions. It holds a set of top-level routes, each with its own back -stack. Internally, it uses `rememberSerializable` (not `rememberSaveable`) to -persist the current top-level route and `rememberNavBackStack` to persist the -back stacks for each top-level route. - -### Step 3.2: Create an object that modifies navigation state in response to events - -Copy the following code into a file named `Navigator.kt`. Add your package name -to match your project structure. - - // package com.example.project - - import androidx.navigation3.runtime.NavKey - - /** - * Handles navigation events (forward and back) by updating the navigation state. - */ - class Navigator(val state: NavigationState){ - fun navigate(route: NavKey){ - if (route in state.backStacks.keys){ - // This is a top level route, just switch to it. - state.topLevelRoute = route - } else { - state.backStacks[state.topLevelRoute]?.add(route) - } - } - - fun goBack(){ - val currentStack = state.backStacks[state.topLevelRoute] ?: - error("Stack for ${state.topLevelRoute} not found") - val currentRoute = currentStack.last() - - // If we're at the base of the current route, go back to the start route stack. - if (currentRoute == state.topLevelRoute){ - state.topLevelRoute = state.startRoute - } else { - currentStack.removeLastOrNull() - } - } - } - -The `Navigator` class provides two navigation event methods: - -- `navigate` to a specific route. -- `goBack` from the current route. - -Both methods modify the `NavigationState`. - -> [!IMPORTANT] -> **Architecture principles:** These classes follow the principles of [Unidirectional Data Flow](references/android/topic/architecture/index.md.txt): -> -> - The `Navigator` handles navigation events and uses them to update `NavigationState`. -> - The UI (provided by `NavDisplay`) observes `NavigationState` and reacts to any changes in that state by updating its UI. - -### Step 3.3: Create the `NavigationState` and `Navigator` - -Create instances of `NavigationState` and `Navigator` with the same scope as -your `NavController`. - - val navigationState = rememberNavigationState( - startRoute = , - topLevelRoutes = - ) - - val navigator = remember { Navigator(navigationState) } - -## Step 4: Replace `NavController` - -Replace `NavController` navigation event methods with `Navigator` equivalents. - -| **`NavController` field or method** | **`Navigator` equivalent** | -|---|---| -| `navigate()` | `navigate()` | -| `popBackStack()` | `goBack()` | - -Replace `NavController` fields with `NavigationState` fields. - -| **`NavController` field or method** | **`NavigationState` equivalent** | -|---|---| -| `currentBackStack` | `backStacks[topLevelRoute]` | -| `currentBackStackEntry` `currentBackStackEntryAsState()` `currentBackStackEntryFlow` `currentDestination` | `backStacks[topLevelRoute].last()` | -| Get the top level route: Traverse up the hierarchy from the current back stack entry to find it. | `topLevelRoute` | - -Use `NavigationState.topLevelRoute` to determine the item that is currently -selected in a navigation bar. - -Before: - - val isSelected = navController.currentBackStackEntryAsState().value?.destination.isRouteInHierarchy(key::class) - - fun NavDestination?.isRouteInHierarchy(route: KClass<*>) = - this?.hierarchy?.any { - it.hasRoute(route) - } ?: false - -After: - - val isSelected = key == navigationState.topLevelRoute - -Verify that you have removed all references to `NavController`, including -any imports. - -## Step 5: Move your destinations from `NavHost`'s `NavGraph` into an `entryProvider` - -In Navigation 2, you [define your destinations](references/android/guide/navigation/design/index.md.txt) -using the [NavGraphBuilder DSL](references/android/guide/navigation/design/kotlin-dsl.md.txt), -usually inside `NavHost`'s trailing lambda. It is common to use extension -functions here as described in [Encapsulate your navigation code](references/android/guide/navigation/design/encapsulate.md.txt). - -In Navigation 3, you define your destinations using an `entryProvider`. This -`entryProvider` resolves a route to a [`NavEntry`](references/android/guide/navigation/navigation-3/basics.md.txt). Importantly, the -`entryProvider` does not define parent-child relationships between entries. - -In this migration guide, parent-child relationships are modelled -as follows: - -- `NavigationState` has a set of top-level routes (the parent routes) and a stack for each one. It keeps track of the current top-level route and its associated stack. -- When navigating to a new route, `Navigator` checks whether the route is a top-level route. If it is, the current top-level route and stack are updated. If it's not, it's a child route and is added to the current stack. - -> [!NOTE] -> **Note:** If your app needs to navigate from an entry in one stack to another, you need to define the parent-child relationships for the routes and update the navigation logic in `Navigator` to support this. - -## Step 5.1: Create an `entryProvider` - -Create an `entryProvider` [using the DSL](references/android/guide/navigation/navigation-3/basics.md.txt) at the same scope as the -`NavigationState`. - - val entryProvider = entryProvider { - - } - -## Step 5.2: Move destinations into the `entryProvider` - -For each destination defined inside `NavHost`, do the following based on the -destination type: - -- `navigation`: Delete it along with the route. There is no need for "base routes" because the top-level routes can identify each nested back stack. -- `composable`: Move it into `entryProvider` and rename it to `entry`, retaining the type parameter. For example, `composable` becomes `entry`. -- `dialog`: Do the same as `composable`, but add metadata to the entry as follows: `entry(metadata = DialogSceneStrategy.dialog())`. -- [`bottomSheet`](https://developer.android.com/reference/kotlin/androidx/compose/material/navigation/package-summary#(androidx.navigation.NavGraphBuilder).bottomSheet(kotlin.String,kotlin.collections.List,kotlin.collections.List,kotlin.Function2)): [Follow the bottom sheet recipe here](https://github.com/android/nav3-recipes/tree/main/app/src/main/java/com/example/nav3recipes/bottomsheet). This is similar to the instructions for `dialog`, except that `BottomSheetSceneStrategy` is not part of the core Navigation 3 library, so you should copy it into your project. - -**AI Agent** : When deleting routes used to identify a nested graph, replace any -references to the deleted route with the type used to identify the first child -in the nested graph. For example if the original code is -`navigation{ composable{ ... } }`, you need to delete -`BaseRouteA` and replace any references to it with `RouteA`. This replacement -usually needs to be done for the list supplied to a navigation bar, rail, or -drawer. - -You can refactor [`NavGraphBuilder` extension functions](references/android/guide/navigation/design/encapsulate.md.txt) to -`EntryProviderScope` extension functions, and then move them. - -Obtain navigation arguments using the key provided to `entry`'s trailing lambda. - -For example: - - import androidx.navigation.NavDestination - import androidx.navigation.NavDestination.Companion.hasRoute - import androidx.navigation.NavDestination.Companion.hierarchy - import androidx.navigation.NavGraphBuilder - import androidx.navigation.compose.NavHost - import androidx.navigation.compose.composable - import androidx.navigation.compose.currentBackStackEntryAsState - import androidx.navigation.compose.dialog - import androidx.navigation.compose.navigation - import androidx.navigation.compose.rememberNavController - import androidx.navigation.navOptions - import androidx.navigation.toRoute - - @Serializable data object BaseRouteA - @Serializable data class RouteA(val id: String) - @Serializable data object BaseRouteB - @Serializable data object RouteB - @Serializable data object RouteD - - NavHost(navController = navController, startDestination = BaseRouteA){ - composable{ - val id = entry.toRoute().id - ScreenA(title = "Screen has ID: $id") - } - featureBSection() - dialog{ ScreenD() } - } - - fun NavGraphBuilder.featureBSection() { - navigation(startDestination = RouteB) { - composable { ScreenB() } - } - } - -becomes: - - import androidx.navigation3.runtime.EntryProviderScope - import androidx.navigation3.runtime.NavKey - import androidx.navigation3.runtime.entryProvider - import androidx.navigation3.scene.DialogSceneStrategy - - @Serializable data class RouteA(val id: String) : NavKey - @Serializable data object RouteB : NavKey - @Serializable data object RouteD : NavKey - - val entryProvider = entryProvider { - entry{ key -> ScreenA(title = "Screen has ID: ${key.id}") } - featureBSection() - entry(metadata = DialogSceneStrategy.dialog()){ ScreenD() } - } - - fun EntryProviderScope.featureBSection() { - entry { ScreenB() } - } - -## Step 6: Replace `NavHost` with `NavDisplay` - -Replace `NavHost` with `NavDisplay`. - -- Delete `NavHost` and replace it with `NavDisplay`. -- Specify `entries = navigationState.toEntries(entryProvider)` as a parameter. This converts the navigation state into the entries that `NavDisplay` shows using the `entryProvider`. -- Connect `NavDisplay.onBack` to `navigator.goBack()`. This causes `navigator` to update the navigation state when `NavDisplay`'s built-in back handler completes. -- If you have dialog destinations, add `DialogSceneStrategy` to `NavDisplay`'s `sceneStrategy` parameter. - -For example: - - import androidx.navigation3.ui.NavDisplay - - NavDisplay( - entries = navigationState.toEntries(entryProvider), - onBack = { navigator.goBack() }, - sceneStrategy = remember { DialogSceneStrategy() } - ) - -## Step 7: Remove Navigation 2 dependencies - -Remove all Navigation 2 imports and library dependencies. - -## Summary - -Congratulations! Your project is now migrated to Navigation 3. If you or your AI -agent has run into any problems using this guide, [file a bug -here](https://issuetracker.google.com/issues/new?component=1750212&template=2102223&title=%5BMigration%5D). \ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/encapsulate.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/encapsulate.md.txt deleted file mode 100644 index 9a10134..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/encapsulate.md.txt +++ /dev/null @@ -1,165 +0,0 @@ -When using the Kotlin DSL to construct your graph, keeping destinations and -navigation events in a single file can be difficult to maintain. This is -especially true if you have multiple independent features. - -## Extract destinations - -You should move your destinations into [`NavGraphBuilder`](https://developer.android.com/reference/kotlin/androidx/navigation/NavGraphBuilder) extension -functions. They should live close to the routes which define them, and the -screens that they display. For example, consider the following app-level code -that creates a destination which shows a list of contacts: - - // MyApp.kt - - @Serializable - object Contacts - - @Composable - fun MyApp() { - ... - NavHost(navController, startDestination = Contacts) { - composable { ContactsScreen( /* ... */ ) } - } - } - -You should move the navigation-specific code into a separate file: - - // ContactsNavigation.kt - - @Serializable - object Contacts - - fun NavGraphBuilder.contactsDestination() { - composable { ContactsScreen( /* ... */ ) } - } - - // MyApp.kt - - @Composable - fun MyApp() { - ... - NavHost(navController, startDestination = Contacts) { - contactsDestination() - } - } - -The routes and destination definitions are now separate from the main app and -you can update them independently. The main app is only dependent on a single -extension function. In this case, that is -`NavGraphBuilder.contactsDestination()`. - -The `NavGraphBuilder` extension function forms the bridge between a stateless -screen-level composable function and Navigation-specific logic. This layer can -also define where the state comes from and how you handle events. - -> [!NOTE] -> **Note:** This is essentially the same approach as when you move screens to different composable functions instead of defining them in one monolithic composable function. - -## Example - -The following snippet introduces a new destination to display a contact's -details, and updates the existing contact list destination to [expose a -navigation event](https://developer.android.com/guide/navigation/use-graph/navigate#nav-from-composable) to display the contact's details. - -Here's a typical set of screens that can be `internal` to their own module, so -that other modules cannot access them: - - // ContactScreens.kt - - // Displays a list of contacts - @Composable - internal fun ContactsScreen( - uiState: ContactsUiState, - onNavigateToContactDetails: (contactId: String) -> Unit - ) { ... } - - // Displays the details for an individual contact - @Composable - internal fun ContactDetailsScreen(contact: ContactDetails) { ... } - -### Create destinations - -The following [`NavGraphBuilder`](https://developer.android.com/reference/kotlin/androidx/navigation/NavGraphBuilder) extension function creates a destination -which shows the `ContactsScreen` composable. In addition, it now connects -the screen with a `ViewModel` that provides the screen UI state and handles the -screen-related business logic. - -Navigation events, such as navigating to the contact details destination, are -exposed to the caller rather than being handled by the `ViewModel`. - - // ContactsNavigation.kt - - @Serializable - object Contacts - - // Adds contacts destination to `this` NavGraphBuilder - fun NavGraphBuilder.contactsDestination( - // Navigation events are exposed to the caller to be handled at a higher level - onNavigateToContactDetails: (contactId: String) -> Unit - ) { - composable { - // The ViewModel as a screen level state holder produces the screen - // UI state and handles business logic for the ConversationScreen - val viewModel: ContactsViewModel = hiltViewModel() - val uiState = viewModel.uiState.collectAsStateWithLifecycle() - ContactsScreen( - uiState, - onNavigateToContactDetails - ) - } - } - -You can use the same approach to create a destination which displays the -`ContactDetailsScreen`. In this case, instead of obtaining the UI state from a -view model, you can obtain it directly from the `NavBackStackEntry`. - - // ContactsNavigation.kt - - @Serializable - internal data class ContactDetails(val id: String) - - fun NavGraphBuilder.contactDetailsScreen() { - composable { navBackStackEntry -> - ContactDetailsScreen(contact = navBackStackEntry.toRoute()) - } - } - -### Encapsulate navigation events - -In the same way that you encapsulate destinations, you can encapsulate -navigation events to avoid exposing route types unnecessarily. Do this by -creating extension functions on `NavController`. - - // ContactsNavigation.kt - - fun NavController.navigateToContactDetails(id: String) { - navigate(route = ContactDetails(id = id)) - } - -### Bring it together - -The navigation code for displaying contacts is now cleanly separated from the -app's navigation graph. The app needs to: - -- Call `NavGraphBuilder` extension functions to create destinations -- Connect those destinations by calling `NavController` extension functions for navigation events - - // MyApp.kt - - @Composable - fun MyApp() { - ... - NavHost(navController, startDestination = Contacts) { - contactsDestination(onNavigateToContactDetails = { contactId -> - navController.navigateToContactDetails(id = contactId) - }) - contactDetailsDestination() - } - } - -## In summary - -- Encapsulate your navigation code for a related set of screens by placing it in a separate file -- Expose destinations by creating extension functions on `NavGraphBuilder` -- Expose navigation events by creating extension functions on `NavController` -- Use `internal` to keep screens and route types private \ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/index.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/index.md.txt deleted file mode 100644 index c7bdac5..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/index.md.txt +++ /dev/null @@ -1,364 +0,0 @@ -The [Navigation component](https://developer.android.com/guide/navigation) uses a *navigation graph* to manage your app's -navigation. The navigation graph is a data structure that contains each -destination within your app and the connections between them. - -> [!NOTE] -> **Note:** The navigation graph is distinct from the [back stack](https://developer.android.com/guide/navigation/backstack), which is a stack within the `NavController` that holds destinations the user has recently visited. - -## Destination types - -There are three general types of destinations: hosted, dialog, and activity. The -following table outlines these three destination types and their purposes. - -| Type | Description | Use cases | -|---|---|---| -| Hosted | Fills the entire navigation host. That is, the size of a hosted destination is the same as the size of the navigation host and previous destinations are not visible. | Main and detail screens. | -| Dialog | Presents overlay UI components. This UI is not tied to the location of the navigation host or its size. Previous destinations are visible underneath the destination. | Alerts, selections, forms. | -| Activity | Represents unique screens or features within the app. | Serve as an exit point to the navigation graph that starts a new Android activity that is managed separately from the Navigation component. In modern Android development, an app consists of a single activity. Activity destinations are therefore best used when interacting with third party activities or as part of [the migration process](https://developer.android.com/guide/navigation/migrate). | - -This document contains examples of hosted destinations, which are the most -common and fundamental destinations. See the following guides for information on -the other destinations: - -- [Dialog destinations](https://developer.android.com/guide/navigation/design/dialog-destinations) -- [Activity destinations](https://developer.android.com/guide/navigation/design/activity-destinations) - -### Frameworks - -Although the same general workflow applies in every case, how exactly you create -a navigation host and graph depends on the UI framework you use. - -- **Compose:** Use the `NavHost` composable. Add a `NavGraph` to it using the [Kotlin DSL](https://developer.android.com/guide/navigation/navigation-kotlin-dsl). You can create the graph in two ways: - - **As part of the NavHost:** Construct the navigation graph directly as part of adding the `NavHost`. - - **Programmatically:** Use the [`NavController.createGraph()`](https://developer.android.com/reference/androidx/navigation/NavController#(androidx.navigation.NavController).createGraph(kotlin.String,kotlin.String,kotlin.Function1)) method to create a `NavGraph` and pass it to the `NavHost` directly. -- **Fragments:** When using fragments with the views UI framework, use a `NavHostFragment` as the host. There are several ways to create a navigation graph: - - **Programmatically:** Use the Kotlin DSL to create a `NavGraph` and directly apply it on the `NavHostFragment`. - - The `createGraph()` function used with the Kotlin DSL for both fragments and Compose is the same. - - **XML:** Write your navigation host and graph directly in XML. - - **Android Studio editor:** Use the GUI editor in Android Studio to create and adjust your graph as an XML resource file. - -> [!NOTE] -> **Note:** How you interact with the graph through the `NavController` is similar between frameworks. See the [Navigate to a destination](https://developer.android.com/guide/navigation/use-graph/navigate) guide for more details. - -## Compose - -In Compose, use a serializable object or class to define a *route*. A route -describes how to get to a destination, and contains all the information that the -destination requires. - -Use the `@Serializable` annotation to automatically create the necessary -serialization and deserialization methods for your route types. This annotation -is provided by the [Kotlin Serialization -plugin](https://kotlinlang.org/docs/serialization.html). [Follow these -instructions to add this plugin](https://developer.android.com/guide/navigation#set-up). - -Once you have defined your routes, use the `NavHost` composable to create your -navigation graph. Consider the following example: - - @Serializable - object Profile - @Serializable - object FriendsList - - val navController = rememberNavController() - - NavHost(navController = navController, startDestination = Profile) { - composable { ProfileScreen( /* ... */ ) } - composable { FriendsListScreen( /* ... */ ) } - // Add more destinations similarly. - } - -1. A serializable object represents each of the two routes, `Profile` and `FriendsList`. -2. The call to the `NavHost` composable passes a `NavController` and a route for the start destination. -3. The lambda passed to the `NavHost` ultimately calls [`NavController.createGraph()`](https://developer.android.com/reference/androidx/navigation/NavController#(androidx.navigation.NavController).createGraph(kotlin.String,kotlin.String,kotlin.Function1)) and returns a `NavGraph`. -4. Each route is supplied as a type argument to [`NavGraphBuilder.composable()`](https://developer.android.com/reference/kotlin/androidx/navigation/NavGraphBuilder#(androidx.navigation.NavGraphBuilder).composable(kotlin.collections.Map,kotlin.collections.List,kotlin.Function1,kotlin.Function1,kotlin.Function1,kotlin.Function1,kotlin.Function1,kotlin.Function2)) which adds the destination to the resulting `NavGraph`. -5. The lambda passed to `composable` is what the `NavHost` displays for that destination. - -> [!CAUTION] -> **Caution:** Instead of passing a type to `composable()`, you can pass a `route` string or an integer `id`. However, this makes it much more difficult to manage passing additional arguments to the destination. - -### Understand the lambda - -To better understand the lambda that creates the `NavGraph`, consider that to -build the same graph as in the preceding snippet, you could create the -`NavGraph` separately using [`NavController.createGraph()`](https://developer.android.com/reference/androidx/navigation/NavController#(androidx.navigation.NavController).createGraph(kotlin.String,kotlin.String,kotlin.Function1)) and pass it to -the `NavHost` directly: - - val navGraph by remember(navController) { - navController.createGraph(startDestination = Profile)) { - composable { ProfileScreen( /* ... */ ) } - composable { FriendsListScreen( /* ... */ ) } - } - } - NavHost(navController, navGraph) - -> [!IMPORTANT] -> **Important:** A [`NavController`](https://developer.android.com/reference/androidx/navigation/NavController) is associated with a single `NavHost` composable. The `NavHost` provides the `NavController` access to its navigation graph. When you use the `NavController` to navigate to a destination, you cause the `NavController` to interact with its associated `NavHost`. - -### Pass arguments - -If you need to pass data to a destination, define the route with a class that -has parameters. For example, the `Profile` route is a data class with a `name` -parameter. - - @Serializable - data class Profile(val name: String) - -Whenever you need to pass arguments to that destination, you create an instance -of your route class, passing the arguments to the class constructor. - -> [!NOTE] -> **Note:** Use a `data class` for a route with arguments, and an `object` or `data -> object` for a route with no arguments. - -For optional arguments, create nullable fields with a default value. - - @Serializable - data class Profile(val nickname: String? = null) - -### Obtain route instance - -You can obtain the route instance with `NavBackStackEntry.toRoute()` or -`SavedStateHandle.toRoute()`. When you create a destination using -`composable()`, the `NavBackStackEntry` is available as a parameter. - - @Serializable - data class Profile(val name: String) - - val navController = rememberNavController() - - NavHost(navController = navController, startDestination = Profile(name="John Smith")) { - composable { backStackEntry -> - val profile: Profile = backStackEntry.toRoute() - ProfileScreen(name = profile.name) } - } - -Note the following in this snippet: - -- The `Profile` route specifies the starting destination in the navigation graph, with `"John Smith"` as the argument for `name`. -- The destination itself is the `composable{}` block. -- The `ProfileScreen` composable takes the value of `profile.name` for its own `name` argument. -- As such, the value `"John Smith"` passes through to `ProfileScreen`. - -### Minimal example - -A complete example of a `NavController` and `NavHost` working together: - - @Serializable - data class Profile(val name: String) - - @Serializable - object FriendsList - - // Define the ProfileScreen composable. - @Composable - fun ProfileScreen( - profile: Profile - onNavigateToFriendsList: () -> Unit, - ) { - Text("Profile for ${profile.name}") - Button(onClick = { onNavigateToFriendsList() }) { - Text("Go to Friends List") - } - } - - // Define the FriendsListScreen composable. - @Composable - fun FriendsListScreen(onNavigateToProfile: () -> Unit) { - Text("Friends List") - Button(onClick = { onNavigateToProfile() }) { - Text("Go to Profile") - } - } - - // Define the MyApp composable, including the `NavController` and `NavHost`. - @Composable - fun MyApp() { - val navController = rememberNavController() - NavHost(navController, startDestination = Profile(name = "John Smith")) { - composable { backStackEntry -> - val profile: Profile = backStackEntry.toRoute() - ProfileScreen( - profile = profile, - onNavigateToFriendsList = { - navController.navigate(route = FriendsList) - } - ) - } - composable { - FriendsListScreen( - onNavigateToProfile = { - navController.navigate( - route = Profile(name = "Aisha Devi") - ) - } - ) - } - } - } - -As the snippet demonstrates, instead of passing the `NavController` to your -composables, expose an event to the `NavHost`. That is, your composables should -have a parameter of type `() -> Unit` for which the `NavHost` passes a lambda -that calls `NavController.navigate()`. - -> [!NOTE] -> **Note:** By using the parameters of the route class you can pass data to the given destination with full type safety. For example, in the previous code `Profile.name` ensures that `name` is always a `String`. - -## Fragments - -As outlined in the preceding sections, when using fragments you have the option -to create a navigation graph programmatically using the Kotlin DSL, XML, or the -Android Studio editor. - -The following sections detail these different approaches. - -> [!NOTE] -> **Note:** The Navigation component is designed for apps that have one main activity with multiple fragment destinations. The main activity is associated with a navigation graph and contains a `NavHostFragment` that is responsible for swapping destinations as needed. In an app with multiple activity destinations, each activity has its own navigation graph. - -### Programmatically - -The Kotlin DSL provides a programmatic way of creating a navigation graph with -fragments. In many ways this is neater and more modern than using an XML -resource file. - -Consider the following example, which implements a two-screen navigation graph. - -First it is necessary to create the `NavHostFragment`, which must *not* include -an `app:navGraph` element: - - - - - - -Next, pass the `id` of the `NavHostFragment` to -[`NavController.findNavController`](https://developer.android.com/reference/androidx/navigation/Navigation#findNavController(android.view.View))(). This associates the NavController with -the `NavHostFragment`. - -Subsequently, the call to [`NavController.createGraph()`](https://developer.android.com/reference/androidx/navigation/NavController#(androidx.navigation.NavController).createGraph(kotlin.String,kotlin.String,kotlin.Function1)) links the graph to -the `NavController` and consequently also to the `NavHostFragment`: - - @Serializable - data class Profile(val name: String) - - @Serializable - object FriendsList - - // Retrieve the NavController. - val navController = findNavController(R.id.nav_host_fragment) - - // Add the graph to the NavController with `createGraph()`. - navController.graph = navController.createGraph( - startDestination = Profile(name = "John Smith") - ) { - // Associate each destination with one of the route constants. - fragment { - label = "Profile" - } - - fragment() { - label = "Friends List" - } - - // Add other fragment destinations similarly. - } - -Using the DSL in this way is very similar to the workflow outlined in the -preceding section on [Compose](https://developer.android.com/guide/navigation/design#compose). For example, both there and here, the -`NavController.createGraph()` function generates the `NavGraph`. Likewise, while -`NavGraphBuilder.composable()` adds composable destinations to the graph, here -[`NavGraphBuilder.fragment()`](https://developer.android.com/reference/kotlin/androidx/navigation/NavGraphBuilder#(androidx.navigation.NavGraphBuilder).fragment(kotlin.Int)) adds a fragment destination. - -For more information on how to use the Kotlin DSL, see [Build a graph with the -NavGraphBuilder DSL](https://developer.android.com/guide/navigation/navigation-kotlin-dsl). - -### XML - -You can directly write the XML yourself. The following example mirrors and is -equivalent to the two-screen example from the preceding section. - -First, create a `NavHostFragment`. This serves as the navigation host which -contains the actual navigation graph. - -A minimal implementation of a `NavHostFragment`: - - - - - - - -The `NavHostFragment` contains the attribute `app:navGraph`. Use this attribute -to connect your navigation graph to the navigation host. The following is an -example of how you might implement the graph: - - - - - - - - - - - - - - -You use actions to define the connections between different destinations. In -this example, the `profile` fragment contains an action that navigates to -`friendslist`. For more information, see [Use Navigation actions and -fragments](https://developer.android.com/guide/navigation/design/actions). - -> [!NOTE] -> **Note:** The DSL example does not define actions because they don't apply in that context. When using the DSL, [use `NavController.navigate()` directly](https://developer.android.com/guide/navigation/design/kotlin-dsl#host). - -### Editor - -You can manage your app's navigation graph using the Navigation Editor in -Android Studio. This is essentially a GUI you can use to create and edit your -`NavigationFragment` XML, as seen in the preceding section. - -For more information, see [Navigation editor](https://developer.android.com/guide/navigation/design/editor). - -## Nested graphs - -You can also use nested graphs. This involves using a graph as a navigation -destination. For more information, see [Nested graphs](https://developer.android.com/guide/navigation/design/nested-graphs). - -## Further Reading - -For more core navigation concepts, see the following guides: - -- **[Overview](https://developer.android.com/guide/navigation):** Make sure to read the general overview of the Navigation component. -- **[Activity destinations](https://developer.android.com/guide/navigation/design/activity-destinations):** Examples of how to implement destinations that take the user to activities. -- **[Dialog destinations](https://developer.android.com/guide/navigation/design/dialog-destinations):** Examples of how to create destinations that take the user to a dialog. -- **[Navigate to a destination](https://developer.android.com/guide/navigation/use-graph/navigate):** A detailed guide that covers how to navigate from one destination to another. -- **[Nested graphs](https://developer.android.com/guide/navigation/design/nested-graphs):** An in-depth guide on how to nest one navigation graph within another. \ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/kotlin-dsl.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/kotlin-dsl.md.txt deleted file mode 100644 index 8db54b3..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/design/kotlin-dsl.md.txt +++ /dev/null @@ -1,409 +0,0 @@ -The Navigation component provides a Kotlin-based domain-specific language, or -DSL, that relies on Kotlin's [type-safe -builders](https://kotlinlang.org/docs/reference/type-safe-builders.html) -. This API lets you declaratively compose your graph in your Kotlin code, rather -than inside an XML resource. This can be useful if you want to build your app's -navigation dynamically. For example, your app could download and cache a -navigation configuration from an external web service and then use that -configuration to dynamically build a navigation graph in your activity's -`onCreate()` function. - -## Dependencies - -To use the Kotlin DSL with Fragments, add the following dependency to your app's -`build.gradle` file: - -### Groovy - -```groovy -dependencies { - def nav_version = "2.9.7" - - api "androidx.navigation:navigation-fragment-ktx:$nav_version" -} -``` - -### Kotlin - -```kotlin -dependencies { - val nav_version = "2.9.7" - - api("androidx.navigation:navigation-fragment-ktx:$nav_version") -} -``` - -## Building a graph - -Here is a basic example based on the [Sunflower -app](https://github.com/android/sunflower/tree/views). For this -example, we have two destinations: `home` and `plant_detail`. The `home` -destination is present when the user first launches the app. This destination -displays a list of plants from the user's garden. When the user selects one of -the plants, the app navigates to the `plant_detail` destination. - -Figure 1 shows these destinations along with the arguments required by the -`plant_detail` destination and an action, `to_plant_detail`, that the app uses -to navigate from `home` to `plant_detail`. -![The Sunflower app has two destinations along with an action that -connects them.](https://developer.android.com/static/images/guide/navigation/navigation-kotlin-dsl-1.png) **Figure 1.** The Sunflower app has two destinations, `home` and `plant_detail`, along with an action that connects them. - -### Hosting a Kotlin DSL Nav Graph - -Before you can build your app's navigation graph, you need a place to host the -graph. This example uses fragments, so it hosts the graph in a -[`NavHostFragment`](https://developer.android.com/reference/androidx/navigation/fragment/NavHostFragment) -inside of a -[`FragmentContainerView`](https://developer.android.com/reference/androidx/fragment/app/FragmentContainerView): - - - - android:layout_width="match_parent" - android:layout_height="match_parent"> - - - - - -Notice that the `app:navGraph` attribute is not set in this example. The graph -isn't defined as a [resource](https://developer.android.com/guide/topics/resources/providing-resources) in -the `res/navigation` folder so it needs to be set as part of the `onCreate()` -process in the activity. - -In XML, an action ties together a destination ID with one or more arguments. -However, when using the Navigation DSL a route can contain arguments as part of -the route. This means that there is no concept of actions when using the DSL. - -The next step is to define the routes that you will use when defining your -graph. - -### Create routes for your graph - -[XML-based navigation graphs](https://developer.android.com/guide/navigation/design#xml) are parsed as part -of the Android build process. A numeric constant is created for each `id` -attribute defined in the graph. These build time generated static IDs are not -available when building your navigation graph at runtime so the Navigation DSL -uses [serializable -types](https://kotlinlang.org/docs/serialization.html) instead of -IDs. Each route is represented by a unique type. - -When dealing with arguments, these are [built into the route -type](https://developer.android.com/jetpack/compose/navigation#nav-with-args). This lets you have type safety -for your navigation arguments. - - @Serializable data object Home - @Serializable data class Plant(val id: String) - -### Build a graph with the NavGraphBuilder DSL - -Once you've defined your routes, you can build the navigation graph. - - val navController = findNavController(R.id.nav_host_fragment) - navController.graph = navController.createGraph( - startDestination = Home - ) { - fragment { - label = resources.getString(R.string.home_title) - } - fragment { - label = resources.getString(R.string.plant_detail_title) - } - } - -In this example, two fragment destinations are defined using the -[`fragment()`](https://developer.android.com/reference/kotlin/androidx/navigation/fragment/package-summary#(androidx.navigation.NavGraphBuilder).fragment(kotlin.collections.Map,kotlin.Function1)) -DSL builder function. This function requires two [type -arguments](https://kotlinlang.org/docs/generics.html#generic-functions) -. - -First, a [`Fragment`](https://developer.android.com/reference/kotlin/androidx/fragment/app/Fragment) class -that provides the UI for this destination. Setting this has the same effect as -setting the `android:name` attribute on fragment destinations that are defined -using XML. - -Second, the route. This must be a serializable type which extends from `Any`. It -should contain any navigation arguments that will be used by this destination, -and their types. - -The function also accepts an optional lambda for additional configuration, such -as the destination label, as well as embedded builder functions for custom -arguments and deep links. - -### Navigating with your Kotlin DSL graph - -Finally, you can navigate from `home` to `plant_detail` using -[`NavController.navigate()`](https://developer.android.com/reference/androidx/navigation/NavController#navigate(kotlin.Any,kotlin.Function1)) -calls: - - private fun navigateToPlant(plantId: String) { - findNavController().navigate(route = PlantDetail(id = plantId)) - } - -In `PlantDetailFragment`, you can obtain the navigation arguments by obtaining -the current -[`NavBackStackEntry`](https://developer.android.com/reference/kotlin/androidx/navigation/NavBackStackEntry) -and calling -[`toRoute`](https://developer.android.com/reference/kotlin/androidx/navigation/NavBackStackEntry#(androidx.navigation.NavBackStackEntry).toRoute()) -on it to obtain the route instance. - - val plantDetailRoute = findNavController().getBackStackEntry().toRoute() - val plantId = plantDetailRoute.id - -If `PlantDetailFragment` is using a `ViewModel`, obtain the route instance using -[`SavedStateHandle.toRoute`](https://developer.android.com/reference/kotlin/androidx/lifecycle/SavedStateHandle#(androidx.lifecycle.SavedStateHandle).toRoute(kotlin.collections.Map)). - - val plantDetailRoute = savedStateHandle.toRoute() - val plantId = plantDetailRoute.id - -The rest of this guide describes common navigation graph elements, destinations, -and how to use them when building your graph. - -## Destinations - -The Kotlin DSL provides built-in support for three destination types: -`Fragment`, `Activity`, and `NavGraph` destinations, each of which has its own -inline extension function available for building and configuring the -destination. - -### Fragment destinations - -The -[`fragment()`](https://developer.android.com/reference/kotlin/androidx/navigation/fragment/package-summary#(androidx.navigation.NavGraphBuilder).fragment(kotlin.collections.Map,kotlin.Function1)) -DSL function can be parameterized with the fragment class for the UI and the -route type used to uniquely identify this destination, followed by a lambda -where you can provide additional configuration as described in the [Navigating -with your Kotlin DSL graph](https://developer.android.com/guide/navigation/design/kotlin-dsl#navigate) section. - - fragment { - label = getString(R.string.fragment_title) - // custom argument types, deepLinks - } - -### Activity destination - -The -[`activity()`](https://developer.android.com/reference/kotlin/androidx/navigation/package-summary#(androidx.navigation.NavGraphBuilder).activity(kotlin.collections.Map,kotlin.Function1)) -DSL function takes a type parameter for the route but is not parameterized to -any implementing activity class. Instead, you set an optional `activityClass` in -a trailing lambda. This flexibility lets you define an activity destination for -an activity that should be launched using an [implicit -intent](https://developer.android.com/guide/components/intents-filters#ExampleSend), where an explicit -activity class wouldn't make sense. As with fragment destinations, you can also -configure a label, custom arguments, and deep links. - - activity { - label = getString(R.string.activity_title) - // custom argument types, deepLinks... - - activityClass = MyActivity::class - } - -### Navigation graph destination - -The -[`navigation()`](https://developer.android.com/reference/kotlin/androidx/navigation/package-summary#(androidx.navigation.NavGraphBuilder).navigation(kotlin.Any,kotlin.collections.Map,kotlin.Function1)) -DSL function can be used to build a [nested navigation -graph](https://developer.android.com/guide/navigation/navigation-nested-graphs). This function takes a type -parameter for the route to assign to this graph. It also takes two arguments: -the route of the starting destination of the graph, and a lambda to further -configure the graph. Valid elements include other destinations, custom argument -types, deep links, and a [descriptive label for the -destination](https://developer.android.com/reference/kotlin/androidx/navigation/NavGraphBuilder#label()). -This label can be useful for binding the navigation graph to UI components using -[`NavigationUI`](https://developer.android.com/guide/navigation/navigation-ui). - - @Serializable data object HomeGraph - @Serializable data object Home - - navigation(startDestination = Home) { - // label, other destinations, deep links - } - -### Supporting custom destinations - -If you're using a [new destination type](https://developer.android.com/guide/navigation/navigation-add-new) -that does not directly support the Kotlin DSL, you can add these destinations to -your Kotlin DSL using -[`addDestination()`](https://developer.android.com/reference/androidx/navigation/NavGraph#addDestination(androidx.navigation.NavDestination)): - - // The NavigatorProvider is retrieved from the NavController - val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply { - route = Graph.CustomDestination.route - } - addDestination(customDestination) - -As an alternative, you can also use the unary plus operator to add a newly -constructed destination directly to the graph: - - // The NavigatorProvider is retrieved from the NavController - +navigatorProvider[CustomNavigator::class].createDestination().apply { - route = Graph.CustomDestination.route - } - -### Providing destination arguments - -Destination arguments can be defined as part of the route class. These can be -defined the same way you would for any Kotlin class. Required arguments are -defined as non-nullable types and optional arguments are defined with default -values. - -The underlying mechanism for representing routes and their arguments is string -based. Using strings to model routes allows navigation state to be stored and -restored from disk during [configuration -changes](https://developer.android.com/guide/topics/resources/runtime-changes) and [system-initiated process -death](https://developer.android.com/topic/libraries/architecture/saving-states#options). For this reason, -each navigation argument needs to be serializable, that is, it should have a -method that converts the in-memory representation of the argument value to a -`String`. - -The [Kotlin serialization -plugin](https://kotlinlang.org/docs/serialization.html) -automatically generates serialization methods for [basic -types](https://kotlinlang.org/docs/basic-types.html) when the -`@Serializable` annotation is added to an object. - - @Serializable - data class MyRoute( - val id: String, - val myList: List, - val optionalArg: String? = null - ) - - fragment - -#### Providing custom types - -For custom argument types, you'll need to provide a custom `NavType` class. This -lets you control exactly how your type is parsed from a route or deep link. - -For example, a route used to define a search screen could contain a class that -represents the search parameters: - - @Serializable - data class SearchRoute(val parameters: SearchParameters) - - @Serializable - @Parcelize - data class SearchParameters( - val searchQuery: String, - val filters: List - ) - -A custom `NavType` could be written as: - - val SearchParametersType = object : NavType( - isNullableAllowed = false - ) { - override fun put(bundle: Bundle, key: String, value: SearchParameters) { - bundle.putParcelable(key, value) - } - override fun get(bundle: Bundle, key: String): SearchParameters { - return bundle.getParcelable(key) as SearchParameters - } - - override fun serializeAsValue(value: SearchParameters): String { - // Serialized values must always be Uri encoded - return Uri.encode(Json.encodeToString(value)) - } - - override fun parseValue(value: String): SearchParameters { - // Navigation takes care of decoding the string - // before passing it to parseValue() - return Json.decodeFromString(value) - } - } - -This can then be used in your Kotlin DSL like any other type: - - fragment( - typeMap = mapOf(typeOf() to SearchParametersType) - ) { - label = getString(R.string.plant_search_title) - } - -When navigating to the destination, create an instance of your route: - - val params = SearchParameters("rose", listOf("available")) - navController.navigate(route = SearchRoute(params)) - -The parameter can be obtained from the route in the destination: - - val searchRoute = navController().getBackStackEntry().toRoute() - val params = searchRoute.parameters - -## Deep links - -Deep links can be added to any destination, just as they can with an XML driven -navigation graph. All of the same procedures defined in [Creating a deep link -for a destination](https://developer.android.com/guide/navigation/navigation-deep-link) apply to the process -of creating a deep link using the Kotlin DSL. - -When creating an [implicit deep link](https://developer.android.com/guide/navigation/navigation-deep-link) -however, you don't have an XML navigation resource that can be analyzed for -`` elements. Therefore, you cannot rely on placing a `` -element in your `AndroidManifest.xml` file and must instead add [intent -filters](https://developer.android.com/training/app-links/deep-linking) to your activity manually. The intent -filter you supply should match the base path, action, and mimetype of -your app's deep links. - -Deep links are added to a destination by calling the `deepLink` function inside -the destination's lambda. It accepts the route as a parameterized type, and a -parameter `basePath` for the base path of the URL used for the deep link. - -You can also add an action and mimetype using the -[`deepLinkBuilder`](https://developer.android.com/reference/kotlin/androidx/navigation/NavDeepLinkDslBuilder) -trailing lambda. - -The following example creates a deep link URI for the `Home` destination. - - @Serializable data object Home - - fragment{ - deepLink(basePath = "www.example.com/home"){ - // Optionally, specify the action and/or mime type that this destination - // supports - action = "android.intent.action.MY_ACTION" - mimeType = "image/*" - } - } - -### URI format - -The deep link URI format is automatically generated from the route's fields -using the following rules: - -- Required parameters are appended as *path parameters* (example: `/{id}`) -- Parameters with a default value (optional parameters) are appended as *query - parameters* (example: `?name={name}`) -- Collections are appended as *query parameters* (example: `?items={value1}&items={value2}`) -- The order of parameters matches the order of the fields in the route - -For example, the following route type: - - @Serializable data class PlantDetail( - val id: String, - val name: String, - val colors: List, - val latinName: String? = null, - ) - -has a generated URI format of: - -`basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}` - -There is no limit to the number of deep links you can add. Each time you call -[`deepLink()`](https://developer.android.com/reference/kotlin/androidx/navigation/NavDestinationBuilder#deepLink(kotlin.String)) -a new deep link is appended to a list that is maintained for that destination. - -## Limitations - -The [Safe Args](https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args) plugin is -incompatible with the Kotlin DSL, as the plugin looks for XML resource files to -generate `Directions` and `Arguments` classes. \ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/index.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/index.md.txt deleted file mode 100644 index b6975cb..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/index.md.txt +++ /dev/null @@ -1,145 +0,0 @@ -[Video](https://www.youtube.com/watch?v=Y0Cs2MQxyIs) - -Navigation refers to the interactions that let users navigate across, into, and -back out from the different pieces of content within your app. - -Android Jetpack's Navigation component includes the [Navigation -library](https://developer.android.com/jetpack/androidx/releases/navigation), [Safe Args Gradle plug-in](https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args), -and tooling to help you implement app navigation. The Navigation component -handles diverse navigation use cases, from straightforward button clicks to more -complex patterns, such as app bars and the navigation drawer. - -> [!IMPORTANT] -> **Important:** The Navigation component also ensures a consistent and predictable user experience by adhering to an established [set of principles](https://developer.android.com/guide/navigation/navigation-principles). - -## Key concepts - -The following table provides an overview of the key concepts in -navigation and the main types that you use to implement them. - -| Concept | Purpose | Type | -|---|---|---| -| Host | A UI element that contains the current navigation destination. That is, when a user navigates through an app, the app essentially swaps destinations in and out of the navigation host. | - **Compose** : [`NavHost`](https://developer.android.com/reference/kotlin/androidx/navigation/compose/package-summary#NavHost(androidx.navigation.NavHostController,androidx.navigation.NavGraph,androidx.compose.ui.Modifier,androidx.compose.ui.Alignment,kotlin.Function1,kotlin.Function1,kotlin.Function1,kotlin.Function1,kotlin.Function1)) - **Fragments** : [`NavHostFragment`](https://developer.android.com/reference/androidx/navigation/fragment/NavHostFragment) | -| Graph | A data structure that defines all the navigation destinations within the app and how they connect together. | [`NavGraph`](https://developer.android.com/reference/androidx/navigation/NavGraph) | -| Controller | The central coordinator for managing navigation between destinations. The controller offers methods for navigating between destinations, handling deep links, managing the back stack, and more. | [`NavController`](https://developer.android.com/reference/androidx/navigation/NavController) | -| Destination | A node in the navigation graph. When the user navigates to this node, the host displays its content. | [`NavDestination`](https://developer.android.com/reference/androidx/navigation/NavDestination) Typically created when constructing the navigation graph. | -| Route | Uniquely identifies a destination and any data required by it. You can navigate using routes. Routes take you to destinations. | Any serializable data type. | - -> [!IMPORTANT] -> **Important:** Whether you are using Compose, views, or a custom UI framework, these concepts always apply when implementing navigation. However, the specific ways in which you use them can differ. - -## Benefits and features - -The Navigation component provides a number of other benefits and features, -including the following: - -- **Animations and transitions:** Provides standardized resources for animations and transitions. -- **Deep linking:** Implements and handles deep links that take the user directly to a destination. -- **UI patterns:** Supports patterns such as navigation drawers and bottom navigation with minimal additional work. -- **Type safety:** Includes support for passing data between destinations with [type safety](https://developer.android.com/guide/navigation/design/type-safety). -- **ViewModel support:** Enables scoping a `ViewModel` to a navigation graph to share UI-related data between the graph's destinations. -- **Fragment transactions:** Fully supports and handles fragment transactions. -- **Back and up:** Handles back and up actions correctly by default. - -> [!NOTE] -> **Note:** If you are using XML for your navigation graphs, use Android Studio's [Navigation Editor](https://developer.android.com/guide/navigation/design/editor) to view and edit your graphs. - -> [!NOTE] -> **Note:** Android 13 introduces predictive back navigation, which works with the Navigation component for Android devices. Implement predictive back navigation in your app as soon as possible. Otherwise, users might experience unexpected behavior in a future Android release. - -## Set up your environment - -To include navigation support in your project, add the following dependencies to -your app's `build.gradle` file: - -### Groovy - -```groovy -plugins { - // Kotlin serialization plugin for type safe routes and navigation arguments - id 'org.jetbrains.kotlin.plugin.serialization' version '2.0.21' -} - -dependencies { - def nav_version = "2.9.7" - - // Jetpack Compose Integration - implementation "androidx.navigation:navigation-compose:$nav_version" - - // Views/Fragments Integration - implementation "androidx.navigation:navigation-fragment:$nav_version" - implementation "androidx.navigation:navigation-ui:$nav_version" - - // Feature module support for Fragments - implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" - - // Testing Navigation - androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" - - // JSON serialization library, works with the Kotlin serialization plugin. - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3" -} -``` - -### Kotlin - -```kotlin -plugins { - // Kotlin serialization plugin for type safe routes and navigation arguments - kotlin("plugin.serialization") version "2.0.21" -} - -dependencies { - val nav_version = "2.9.7" - - // Jetpack Compose integration - implementation("androidx.navigation:navigation-compose:$nav_version") - - // Views/Fragments integration - implementation("androidx.navigation:navigation-fragment:$nav_version") - implementation("androidx.navigation:navigation-ui:$nav_version") - - // Feature module support for Fragments - implementation("androidx.navigation:navigation-dynamic-features-fragment:$nav_version") - - // Testing Navigation - androidTestImplementation("androidx.navigation:navigation-testing:$nav_version") - - // JSON serialization library, works with the Kotlin serialization plugin - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") -} -``` - -For information on adding other architecture components to your project, see -[Add components to your project](https://developer.android.com/topic/libraries/architecture/adding-components#navigation). - -## Next steps - -For more documentation and resources related to the Navigation component, see -the following resources. - -### Detailed guides - -For more information on how to implement a navigation host and `NavController`, -as well as detail on how they interact with Compose and other UI frameworks, see -the following guides: - -- [Create a navigation controller](https://developer.android.com/guide/navigation/navcontroller): Outlines how to create a `NavController`. -- [Create your navigation graph](https://developer.android.com/guide/navigation/design): Details how to create a navigation host and a navigation graph. -- [Navigate to a destination](https://developer.android.com/guide/navigation/use-graph/navigate): Demonstrates how to use a `NavController` to move between the destinations in your graph. - -### Codelabs - -- [Learn Jetpack Navigation](https://developer.android.com/codelabs/android-navigation) -- [Fragments and the Navigation Component](https://developer.android.com/codelabs/basic-android-kotlin-training-fragments-navigation-component) -- [Build an adaptive app with dynamic navigation](https://developer.android.com/codelabs/basic-android-kotlin-compose-adaptive-navigation-for-large-screens#0) - -### Videos - -- [Navigating navigation](https://www.youtube.com/watch?v=09qjn706ITA) -- [10 best practices for moving to a single activity](https://www.youtube.com/watch?v=9O1D_Ytk0xg) -- [Single activity: Why, when, and how (Android Dev Summit '18)](https://www.youtube.com/watch?v=2k8x8V77CrU) -- [Android Jetpack: Manage UI navigation with navigation controller (Google - I/O '18)](https://www.youtube.com/watch?v=8GCXtCjtg40) - -### Samples \ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/basics.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/basics.md.txt deleted file mode 100644 index 32633fe..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/basics.md.txt +++ /dev/null @@ -1,205 +0,0 @@ -Navigation describes the way users move around your app. Users interact with UI -elements, usually by tapping or clicking on them, and the app responds by -displaying new content. If the user wants to go back to the previous content, -they use the back gesture or tap the back button. - -## Modeling navigation state - -A convenient way of modeling this behavior is with a stack of content. As the -user navigates *forward* to new content, it is pushed on top of the stack. When -they go *back* from that content, it is popped off the stack and the previous -content is displayed. In navigation terms, this stack is usually referred to as -the **back stack** because it represents the content that the user *can go back -to*. -![A software keyboard action button (a checkmark icon) circled in red.](https://developer.android.com/static/images/topic/libraries/architecture/back-stack.png) **Figure 1.** Diagram showing how the back stack changes with user navigation events. - -## Create a back stack - -In Navigation 3, the back stack does not actually contain content. Instead, it -contains *references to content* , known as **keys**. Keys can be any type but -are usually simple, serializable data classes. Using references rather than -content has the following benefits: - -- It is simple to navigate by pushing keys onto the back stack. -- As long as the keys are serializable, the back stack can be saved to persistent storage, allowing it to survive configuration changes and process death. This is important because users expect to leave your app, come back to it later, and pick up where they left off with the same content being displayed. See [Save your back stack](https://developer.android.com/guide/navigation/navigation-3/save-state#save-back) for more information. - -A key concept in the Navigation 3 API is that you own the back stack. The -library: - -- Expects that your back stack will be a snapshot-state backed `List`, where `T` is the type of your back stack `keys`. You can use `Any` or you can provide your own, more strongly-typed keys. When you see the terms "push" or "pop", the underlying implementation is to add or remove items from the end of a list. -- Observes your back stack and reflects its state in the UI using a [`NavDisplay`](https://developer.android.com/reference/kotlin/androidx/navigation3/ui/package-summary#NavDisplay(kotlin.collections.List,androidx.compose.ui.Modifier,androidx.compose.ui.Alignment,kotlin.Function0,kotlin.collections.List,androidx.navigation3.scene.SceneStrategy,androidx.compose.animation.SharedTransitionScope,androidx.compose.animation.SizeTransform,kotlin.Function1,kotlin.Function1,kotlin.Function2,kotlin.Function1)). - -The following example shows how to create keys and a back stack, and modify the -back stack in response to user navigation events: - - -```kotlin -// Define keys that will identify content -data object ProductList -data class ProductDetail(val id: String) - -@Composable -fun MyApp() { - - // Create a back stack, specifying the key the app should start with - val backStack = remember { mutableStateListOf(ProductList) } - - // Supply your back stack to a NavDisplay so it can reflect changes in the UI - // ...more on this below... - - // Push a key onto the back stack (navigate forward), the navigation library will reflect the change in state - backStack.add(ProductDetail(id = "ABC")) - - // Pop a key off the back stack (navigate back), the navigation library will reflect the change in state - backStack.removeLastOrNull() -} -``` - -
- -## Resolve keys to content - -Content is modeled in Navigation 3 using [`NavEntry`](https://developer.android.com/reference/kotlin/androidx/navigation3/runtime/NavEntry), which is a class -containing a composable function. It represents a *destination* - a single piece -of content that the user can navigate *forward to* and *back from*. - -A `NavEntry` can also contain metadata - information about the content. This -metadata can be read by container objects, like `NavDisplay`, to help them -decide how to display the `NavEntry`'s content. For example, metadata can be -used to override the default animations for a specific `NavEntry`. NavEntry -`metadata` is a map of `String` keys to `Any` values, providing versatile data -storage. - -To convert a `key` to a `NavEntry`, create an Entry Provider. This is a -function that accepts a `key` and returns a `NavEntry` for that `key`. It is -usually defined as a lambda parameter when creating a `NavDisplay`. - -There are two ways to create an Entry Provider, either by creating a lambda -function directly, or by using the [`entryProvider`](https://developer.android.com/reference/kotlin/androidx/navigation3/runtime/package-summary#entryProvider(kotlin.Function1,kotlin.Function1)) DSL. - -### Create an Entry Provider function directly - -You typically create an Entry Provider function using a `when` statement, with -a branch for each of your keys. - - -```kotlin -entryProvider = { key -> - when (key) { - is ProductList -> NavEntry(key) { Text("Product List") } - is ProductDetail -> NavEntry( - key, - metadata = mapOf("extraDataKey" to "extraDataValue") - ) { Text("Product ${key.id} ") } - - else -> { - NavEntry(Unit) { Text(text = "Invalid Key: $it") } - } - } -} -``` - -
- -### Use the `entryProvider` DSL - -The `entryProvider` DSL can simplify your lambda function by avoiding the need -to test against each of your key types, and construct a `NavEntry` for each one. -Use the `entryProvider` builder function for this. It also includes default -fallback behavior (throwing an error) if the key isn't found. - - -```kotlin -entryProvider = entryProvider { - entry { Text("Product List") } - entry( - metadata = mapOf("extraDataKey" to "extraDataValue") - ) { key -> Text("Product ${key.id} ") } -} -``` - -
- -Note the following from the snippet: - -- `entry` is used to define a `NavEntry` with the given type and composable content -- `entry` accepts a `metadata` parameter to set `NavEntry.metadata` - -## Display the back stack - -The back stack represents your app's navigation state. Whenever the back stack -changes, the app UI should reflect the new back stack state. In Navigation 3, a -`NavDisplay` observes your back stack and updates its UI accordingly. Construct -it with following parameters: - -- Your back stack - this should be of type `SnapshotStateList`, where `T` is the type of your back stack keys. It is an observable `List` so that it triggers recomposition of `NavDisplay` when it changes. -- An `entryProvider` to convert the keys in your back stack to `NavEntry` objects. -- Optionally, supply a lambda to the `onBack` parameter. This is called when the user triggers a back event. - -The following example shows how to create a `NavDisplay`. - - -```kotlin -data object Home -data class Product(val id: String) - -@Composable -fun NavExample() { - - val backStack = remember { mutableStateListOf(Home) } - - NavDisplay( - backStack = backStack, - onBack = { backStack.removeLastOrNull() }, - entryProvider = { key -> - when (key) { - is Home -> NavEntry(key) { - ContentGreen("Welcome to Nav3") { - Button(onClick = { - backStack.add(Product("123")) - }) { - Text("Click to navigate") - } - } - } - - is Product -> NavEntry(key) { - ContentBlue("Product ${key.id} ") - } - - else -> NavEntry(Unit) { Text("Unknown route") } - } - } - ) -} -``` - -
- -By default, the `NavDisplay` shows the topmost `NavEntry` on the back stack in a -single pane layout. The following recording shows this app running: -![`NavDisplay` default behavior with two -destinations.](https://developer.android.com/static/images/topic/libraries/architecture/nav3.gif) **Figure 2.** `NavDisplay` default behavior with two destinations. - -## Putting it all together - -The following diagram shows how data flows between the various objects in -Navigation 3: -![A visualization of how data flows between the various objects in Navigation 3.](https://developer.android.com/static/images/topic/libraries/architecture/data-flow.png) **Figure 3.** Diagram showing how data flows through various objects in Navigation 3. - -1. **Navigation events initiate changes**. Keys are added or removed from the - back stack in response to user interactions. - -2. **Change in back stack state triggers content retrieval** . The `NavDisplay` - (a composable that renders a back stack) observes the back stack. In its - default configuration, it displays the topmost back stack entry in a single - pane layout. When the top key on the back stack changes, the `NavDisplay` uses - this key to request the corresponding content from the entry provider. - -3. **Entry provider supplies content** . The entry provider is a function that - resolves a key to a `NavEntry`. Upon receiving a key from the `NavDisplay`, - the entry provider provides the associated `NavEntry`, which contains both the - key and the content. - -4. **Content is displayed** . The `NavDisplay` receives the `NavEntry` and - displays the content. \ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/get-started.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/get-started.md.txt deleted file mode 100644 index d02f0c2..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/get-started.md.txt +++ /dev/null @@ -1,60 +0,0 @@ -To get started with Navigation 3, add the library to your project along with any -supporting libraries. Use the following table to decide which libraries to add: - -## Artifacts - -| **Name** | **What it does** | **Artifact** | -|---|---|---| -| Navigation 3 runtime library | Core Navigation 3 API. Includes `NavEntry`, `EntryProvider` and the associated DSL. | [androidx.navigation3:navigation3-runtime](https://developer.android.com/jetpack/androidx/releases/navigation3) | -| Navigation 3 UI library | Provides classes to display content, including `NavDisplay` and `Scene`. | [androidx.navigation3:navigation3-ui](https://developer.android.com/jetpack/androidx/releases/navigation3) | -| ViewModel Lifecycle for Navigation 3 | Allows ViewModels to be scoped to entries in the back stack. | [androidx.lifecycle:lifecycle-viewmodel-navigation3](https://developer.android.com/jetpack/androidx/releases/lifecycle#lifecycle.lifecycle-viewmodel-navigation3) | -| Material 3 adaptive layouts for Navigation 3 | Provides adaptive layouts (SceneStrategies, Scenes and metadata definitions) for use with NavDisplay. | [androidx.compose.material3.adaptive:adaptive-navigation3](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive) | -| [KotlinX Serialization](https://kotlinlang.org/docs/serialization.html) | Allows navigation keys to be serialized. | Plugin: org.jetbrains.kotlin.plugin.serialization Library: org.jetbrains.kotlinx:kotlinx-serialization-core | - -## Project setup - -To add the Navigation 3 library to your existing project, add the following to -your `libs.versions.toml`: - - [versions] - nav3Core = "1.0.1" - lifecycleViewmodelNav3 = "2.11.0-alpha01" - kotlinSerialization = "2.2.21" - kotlinxSerializationCore = "1.9.0" - material3AdaptiveNav3 = "1.3.0-alpha09" - - [libraries] - # Core Navigation 3 libraries - androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } - androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } - - # Optional add-on libraries - androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } - kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } - androidx-material3-adaptive-navigation3 = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation3", version.ref = "material3AdaptiveNav3" } - - [plugins] - # Optional plugins - jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerialization"} - -Also, update your compile SDK to 36 or later: - - [versions] - compileSdk = "36" - -Add the following to your **app** build file `app/build.gradle.kts`: - - plugins { - ... - // Optional, provides the @Serialize annotation for autogeneration of Serializers. - alias(libs.plugins.jetbrains.kotlin.serialization) - } - - dependencies { - ... - implementation(libs.androidx.navigation3.ui) - implementation(libs.androidx.navigation3.runtime) - implementation(libs.androidx.lifecycle.viewmodel.navigation3) - implementation(libs.androidx.material3.adaptive.navigation3) - implementation(libs.kotlinx.serialization.core) - } \ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/save-state.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/save-state.md.txt deleted file mode 100644 index cfd6e6c..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/guide/navigation/navigation-3/save-state.md.txt +++ /dev/null @@ -1,89 +0,0 @@ -The following sections describe strategies for saving your back stack and -storing state associated with entries on your back stack. - -## Save your back stack - -Ensuring your app's navigation state persists across various lifecycle events, -including configuration changes and process death, is crucial for a good user -experience. In Navigation 3, you own your back stack, so there aren't strict -guidelines on how you should create or save it. However, Navigation 3 does offer -a convenience method that provides you with a saveable back stack: -[`rememberNavBackStack`](https://developer.android.com/reference/kotlin/androidx/navigation3/runtime/package-summary#rememberNavBackStack(kotlin.Array)). - -### Use `rememberNavBackStack` - -The `rememberNavBackStack` composable function is designed to create a back -stack that persists across configuration changes and process death. - -For `rememberNavBackStack` to function correctly, each key in your back stack -must adhere to specific requirements: - -- **Implement `NavKey` interface** : Every key in the back stack must implement the [`NavKey`](https://developer.android.com/reference/kotlin/androidx/navigation3/runtime/NavKey) interface. This acts as a marker interface that signals to the library that the key can be saved. -- **Have the `@Serializable` annotation** : In addition to implementing `NavKey`, your key classes and objects must be marked with the `@Serializable` annotation. - -The following snippet shows a correct implementation of `rememberNavBackStack`: - - -```kotlin -@Serializable -data object Home : NavKey - -@Composable -fun NavBackStack() { - val backStack = rememberNavBackStack(Home) -} -``` - -
- -### Alternative: Storing in a `ViewModel` - -Another approach to managing your back stack is to store it in a `ViewModel`. -For persistence through process death when using a `ViewModel` or any other -custom storage, you need to: - -- **Ensure your keys are serializable** : Just like with `rememberNavBackStack`, your navigation keys must be serializable. -- **Handle serialization and deserialization manually** : You're responsible for manually saving the serialized representation of each key to, and deserializing it from, persistent storage (e.g., `SharedPreferences`, a database, or a file) when your app is going into the background or being restored. - -## Scoping `ViewModel`s to `NavEntry`s - -`ViewModels` are used to retain UI-related state across configuration changes, -such as screen rotations. By default, `ViewModels` are scoped to the nearest -`ViewModelStoreOwner`, which is typically your `Activity` or `Fragment`. - -However, you might want to scope a `ViewModel` to a specific `NavEntry` (i.e., a -specific screen or destination) on the back stack, rather than the entire -`Activity`. This ensures that the `ViewModel`'s state is retained only while -that particular `NavEntry` is part of the back stack, and is cleared when the -`NavEntry` is popped. - -The `androidx.lifecycle:lifecycle-viewmodel-navigation3` add-on library provides -a [`NavEntryDecorator`](https://developer.android.com/guide/navigation/navigation-3/naventrydecorators) that facilitates this. This decorator provides a -`ViewModelStoreOwner` for each `NavEntry`. When you create a `ViewModel` inside a -`NavEntry`'s content (e.g., using `viewModel()` in Compose), it is automatically -scoped to that specific `NavEntry`'s key on the back stack. This means the -`ViewModel` is created when the `NavEntry` is added to the back stack, and -cleared when it's removed. - -To use [`NavEntryDecorator`](https://developer.android.com/guide/navigation/navigation-3/naventrydecorators#apply-decorators) for scoping `ViewModel`s to `NavEntry`s, follow -these steps: - -1. Add the `androidx.lifecycle:lifecycle-viewmodel-navigation3` dependency to your `app/build.gradle.kts` file. -2. Add the default [`rememberSaveableStateHolderNavEntryDecorator()`](https://developer.android.com/reference/kotlin/androidx/navigation3/runtime/package-summary#rememberSaveableStateHolderNavEntryDecorator(androidx.compose.runtime.saveable.SaveableStateHolder)) to the list of `entryDecorators` when constructing a `NavDisplay`. -3. Add [`rememberViewModelStoreNavEntryDecorator()`](https://developer.android.com/reference/kotlin/androidx/lifecycle/viewmodel/navigation3/package-summary#rememberViewModelStoreNavEntryDecorator(androidx.lifecycle.ViewModelStoreOwner,kotlin.Function0)) to the list of `entryDecorators`. - - -```kotlin -NavDisplay( - entryDecorators = listOf( - // Add the default decorators for managing scenes and saving state - rememberSaveableStateHolderNavEntryDecorator(), - // Then add the view model store decorator - rememberViewModelStoreNavEntryDecorator() - ), - backStack = backStack, - entryProvider = entryProvider { }, -) -``` - -
\ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/studio/gemini/overview.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/studio/gemini/overview.md.txt deleted file mode 100644 index 1dd7a19..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/studio/gemini/overview.md.txt +++ /dev/null @@ -1,56 +0,0 @@ -> [!NOTE] -> **Note:** Gemini in Android Studio is only available in the latest stable channel version of Android Studio and major versions released in the previous 10 months. If you are using an older version of Android Studio, you will need to update to use Gemini. For more information, see [Android Studio and Cloud services -> compatibility](https://developer.android.com/studio/releases#service-compat). - -Gemini in Android Studio is your AI-powered coding companion for Android -development. It helps you be more productive by answering your development -queries, generating code, finding relevant resources, and encouraging best -practices. Most importantly, Gemini in Android Studio is uniquely equipped to -accelerate Android development---it can help you mock up and troubleshoot Compose -UIs, fix Gradle build errors, analyze crashes through integrations with Logcat -and App Quality Insights, and more. -[Video](https://www.youtube.com/watch?v=zGK1vIX87vw) - -Gemini in Android Studio is available in the following editions: - -- [No-cost tier](https://developer.android.com/studio/gemini/get-started) -- [Business tier](https://developer.android.com/studio/gemini/get-started-businesses), available through a subscription to [Gemini Code Assist](https://developers.google.com/gemini-code-assist/docs/overview) or membership in the [Google Developer Program](https://developers.google.com/program). - - Standard tier - - Enterprise tier - -## What is the right tier for me? - -If you're an individual freelance developer, student, or hobbyist we recommend -using [Gemini in Android Studio](https://developer.android.com/studio/gemini/get-started), available at no -cost. This includes access to a lightweight version of Gemini 2.5 Pro with a -smaller context window, which is appropriate for most tasks. If you need -to execute more complex tasks that would benefit from Gemini 2.5 Pro's full 1M -token context window, you can [add a Gemini API key](https://developer.android.com/studio/gemini/add-api-key) -and pay per-token. - -If you're a professional developer who works as part of a team, ask your -administrator to consider subscribing to the [business tier](https://developer.android.com/studio/gemini/get-started-businesses). Gemini for -businesses includes additional features that make it easier to collaborate with -your teammates while incorporating your company's best practices and standards, -including but not limited to: - -- Expanded 1M token context window -- [IP indemnification](https://cloud.google.com/gemini/docs/discover/works#how-gemini-protects) -- [VPC-SC and Private Google Access](https://developers.google.com/gemini-code-assist/docs/configure-vpc-service-controls) -- Integration with tools such as Firebase, Colab Enterprise, BigQuery data insights, Cloud Run, and Database Studio. -- Analytics to track your team's usage and productivity impact. -- (Enterprise tier only) Customized code suggestions from your codebases in external tools such as GitHub. - -For more details about the different offerings, see -[Feature comparison](https://developer.android.com/studio/gemini/feature-comparison). - -## What's new in Gemini in Android Studio - -Gemini in Android Studio is under active development and updates frequently! You -can learn about the latest AI features and other preview features by checking -out the [Android Studio preview release notes](https://developer.android.com/studio/preview/features) and by -[downloading a preview](https://developer.android.com/studio/preview). - -## Permissions - -See [service permissions](https://developer.android.com/studio/services#service-permissions). \ No newline at end of file diff --git a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/topic/architecture/index.md.txt b/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/topic/architecture/index.md.txt deleted file mode 100644 index d218949..0000000 --- a/navigation/navigation3/migrate-from-navigation-2-to-navigation-3/references/android/topic/architecture/index.md.txt +++ /dev/null @@ -1,358 +0,0 @@ -App architecture is the foundation of a high-quality Android application. A -well-defined architecture enables you to create a scalable, maintainable app -that can adapt to the ever-expanding ecosystem of Android devices, including -phones, tablets, foldables, ChromeOS devices, car displays, and XR. - -## App composition - -A typical Android app is composed of multiple [app components](https://developer.android.com/guide/components/fundamentals#components), -such as [services](https://developer.android.com/guide/components/services), [content providers](https://developer.android.com/guide/topics/providers/content-providers), and [broadcast -receivers](https://developer.android.com/guide/components/broadcasts). You declare these components in your -[app manifest](https://developer.android.com/guide/topics/manifest/manifest-intro). - -The user interface of an app is also a component. Historically, UIs were built -using multiple [activities](https://developer.android.com/guide/components/activities/intro-activities). However, modern apps use a -single-activity architecture. A single `Activity` serves as a container for -screens implemented as [fragments](https://developer.android.com/guide/fragments) or Jetpack Compose destinations. - -### Multiple form factors - -Apps can run on multiple form factors, including not just phones, but also -tablets, foldables, ChromeOS devices, and more. An app can't assume a portrait -or landscape orientation. Configuration changes, such as device rotation or -folding and unfolding a foldable device, force your app to recompose its UI, -which affects app data and state. - -### Resource constraints - -Mobile devices---even large screen devices---are resource constrained, -so at any time, the operating system might stop some app processes to make room -for new ones. - -### Variable launch conditions - -In a resource-constrained environment, the components of your app can be -launched individually and out of order; what's more, the operating system or -user can destroy them at any time. As a result, don't store any application data -or state in your app components. Your app components should be self-contained, -independent of each other. - -## Common architectural principles - -If you can't use app components to store application data and state, how -should you design your app? - -As Android apps grow in size, it's important to define an architecture that -allows the app to scale. A well-designed app architecture defines the boundaries -between parts of the app and the responsibilities each part should have. - -### Separation of concerns - -Design your app architecture to follow a few specific principles. - -The most important principle is [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns). It's -a common mistake to write all your code in an [`Activity`](https://developer.android.com/guide/components/activities/intro-activities) or a -[`Fragment`](https://developer.android.com/guide/fragments). - -The primary role of an `Activity` or `Fragment` is to host your app's UI. The -Android OS controls their lifecycle, frequently destroying and recreating them -in response to user actions like screen rotation or system events like low -memory. - -This ephemeral nature makes them unsuitable for holding application data or -state. If you store data in an `Activity` or `Fragment`, that data is lost when -the component is recreated. To ensure data persistence and provide a stable user -experience, don't entrust state to these UI components. - -### Adaptive layouts - -Your app should gracefully handle configuration changes, such as device -[orientation changes](https://developer.android.com/develop/ui/compose/layouts/adaptive/app-orientation-aspect-ratio-resizability) or changes in the size of the app window. -Implement the adaptive [canonical layouts](https://developer.android.com/develop/ui/compose/layouts/adaptive/canonical-layouts) to provide an -optimal user experience on a variety of form factors. - -### Drive UI from data models - -Another important principle is that you should drive your UI from data models, -preferably persistent models. Data models represent the data of an app. They're -independent from the UI elements and other components in your app. This means -that they are not tied to the UI and app component lifecycle but will still be -destroyed when the OS removes the app's process from memory. - -Persistent models are ideal for the following reasons: - -- Users don't lose data if the Android OS destroys your app to free up - resources. - -- Your app continues to work in cases when a network connection is - intermittent or unavailable. - -Base your app architecture on data model classes to make your app robust and -testable. - -### Single source of truth - -When a new data type is defined in your app, you should assign a single source -of truth (SSOT) to it. The SSOT is the *owner* of that data, and only the SSOT -can modify or mutate it. To achieve this, the SSOT exposes the data using an -immutable type; to modify the data, the SSOT exposes functions or receives -events that other types can call. - -This pattern has multiple benefits: - -- Centralizes all changes to a particular type of data in one place -- Protects the data so that other types cannot tamper with it -- Makes changes to the data more traceable, and so, bugs are easier to spot - -In an offline-first application, the source of truth for application data is -typically a database. In some other cases, the source of truth can be a -[`ViewModel`](https://developer.android.com/topic/libraries/architecture/viewmodel). - -### Unidirectional data flow - -The [single source of truth principle](https://developer.android.com/topic/architecture#single-source-of-truth) is often used -with the unidirectional data flow (UDF) pattern. In UDF, **state** flows in only -one direction, typically from parent component to child component. The events -that modify the data flow in the opposite direction. - -In Android, state or data usually flow from the higher-scoped types of the -hierarchy to the lower-scoped ones. Events are usually triggered from the -lower-scoped types until they reach the SSOT for the corresponding data type. -For example, application data usually flows from data sources to the UI. User -events such as button presses flow from the UI to the SSOT where the -application data is modified and exposed in an immutable type. - -This pattern better maintains data consistency, is less prone to errors, is -easier to debug, and provides all the benefits of the SSOT pattern. - -## Recommended app architecture - -Considering common architectural principles, each application should have at -least two layers: - -- **UI layer:** Displays application data on the screen -- **Data layer:** Contains the business logic of your app and exposes application data - -You can add an additional layer called the *domain layer* to simplify and reuse -the interactions between the UI and data layers. -![In a typical app architecture, the UI layer gets the application data -from the data layer or from the optional domain layer, which sits between -the UI layer and the data layer.](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-overview.png) **Figure 1.** Diagram of a typical app architecture. - -> [!NOTE] -> **Note:** The arrows in the diagrams in this guide represent dependencies between classes. For example, the domain layer depends on data layer classes. - -### Modern app architecture - -A modern Android app architecture uses the following techniques (among -others): - -- Adaptive and layered architecture -- Unidirectional data flow (UDF) in all layers of the app -- UI layer with state holders to manage the complexity of the UI -- Coroutines and flows -- Dependency injection best practices - -For more information, see -[Recommendations for Android architecture](https://developer.android.com/topic/architecture/recommendations). - -### UI layer - -The role of the UI layer (or *presentation layer*) is to display the application -data on screen. Whenever the data changes, either due to user interaction -(such as pressing a button) or external input (such as a network response), the -UI should update to reflect the changes. - -The UI layer comprises two types of constructs: - -- UI elements that render the data on the screen. You build these elements using [Jetpack Compose](https://developer.android.com/jetpack/compose) functions to support adaptive layouts. -- State holders (such as [`ViewModel`](https://developer.android.com/topic/libraries/architecture/viewmodel)) that hold data, expose it to the UI, and handle logic - -![In a typical architecture, the UI layer's UI elements depend on state -holders, which in turn depend on classes from either the data layer or the -optional domain layer.](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-overview-ui.png) **Figure 2.** The role of the UI layer in app architecture. - -For adaptive UIs, state holders such as `ViewModel` objects expose UI state that -adapts to different [window size classes](https://developer.android.com/develop/ui/compose/layouts/adaptive/window-size-classes). You can use -`currentWindowAdaptiveInfo()` to derive this UI state. Components like -`NavigationSuiteScaffold` can then use this information to automatically switch -between different navigation patterns (for example, `NavigationBar`, -`NavigationRail`, or `NavigationDrawer`) based on the available screen space. - -To learn more, see the [UI layer page](https://developer.android.com/jetpack/guide/ui-layer). - -### Data layer - -The data layer of an app contains the *business logic*. Business logic is what -gives value to your app---it comprises rules that determine how your app -creates, stores, and changes data. - -The data layer is made up of repositories each of which can contain zero to many -data sources. You should create a repository class for each different type of -data you handle in your app. For example, you might create a `MoviesRepository` -class for data related to movies or a `PaymentsRepository` class for data -related to payments. -![In a typical architecture, the data layer's repositories provide data -to the rest of the app and depend on the data sources.](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-overview-data.png) **Figure 3.** The role of the data layer in app architecture. - -Repository classes are responsible for the following: - -- Exposing data to the rest of the app -- Centralizing changes to the data -- Resolving conflicts between multiple data sources -- Abstracting sources of data from the rest of the app -- Containing business logic - -Each data source class should have the responsibility of working with only one -source of data, which can be a file, a network source, or a local database. -Data-source classes are the bridge between the application and the system for -data operations. - -To learn more, see the [data layer page](https://developer.android.com/jetpack/guide/data-layer). - -### Domain layer - -The domain layer is an optional layer between the UI and data layers. - -The domain layer is responsible for encapsulating complex business logic or -simpler business logic that is reused by multiple view models. The domain layer -is optional because not all apps have these requirements. Use it only when -needed, for example, to handle complexity or favor reusability. -![When it is included, the optional domain layer provides dependencies to -the UI layer and depends on the data layer.](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-overview-domain.png) **Figure 4.** The role of the domain layer in app architecture. - -Classes in the domain layer are commonly called *use cases* or *interactors* . -Each use case should have responsibility for a single functionality. For -example, your app could have a `GetTimeZoneUseCase` class if multiple view -models rely on time zones to display the proper message on the screen. - -To learn more, see the [domain layer page](https://developer.android.com/jetpack/guide/domain-layer). - -## Manage dependencies between components - -Classes in your app depend on other classes to function properly. You can use -either of the following design patterns to gather the dependencies of a -particular class: - -- [Dependency injection (DI)](https://developer.android.com/training/dependency-injection): Dependency injection allows classes to define their dependencies without constructing them. At runtime, another class is responsible for providing these dependencies. -- [Service locator](https://en.wikipedia.org/wiki/Service_locator_pattern): The service locator pattern provides a registry where classes can obtain their dependencies instead of constructing them. - -These patterns allow you to scale your code because they provide clear patterns -for managing dependencies without duplicating code or adding complexity. The -patterns also allow you to quickly switch between test and production -implementations. - -> [!IMPORTANT] -> **Key Point:** Use the dependency injection pattern and the [Hilt library](https://developer.android.com/training/dependency-injection/hilt-android) in Android apps. Hilt automatically constructs objects by walking the dependency tree, provides compile-time verification of dependencies, and creates dependency containers for Android framework classes. - -## General best practices - -Programming is a creative field, and building Android apps isn't an exception. -There are many ways to solve a problem; you might communicate data between -multiple activities or fragments, retrieve remote data and persist it locally -for offline mode, or handle any number of other common scenarios that nontrivial -apps encounter. - -Although the following recommendations aren't mandatory, in most cases following -them makes your codebase more robust, testable, and maintainable. - -**Don't store data in app components.** - -Avoid designating your app's entry points---such as activities, services, -and broadcast receivers---as sources of data. The entry points should only -coordinate with other components to retrieve the subset of data that is relevant -to that entry point. Each app component is short‑lived, depending -on the user's interaction with their device and capacity of the system. - -**Reduce dependencies on Android classes.** - -Your app components should be the only classes that rely on Android framework -SDK APIs such as [`Context`](https://developer.android.com/reference/android/content/Context) or [`Toast`](https://developer.android.com/guide/topics/ui/notifiers/toasts). Abstracting other classes in your -app away from the app components helps with testability and reduces -[coupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming)) within your app. - -**Define clear boundaries of responsibility between modules in your app.** - -Don't spread the code that loads data from the network across multiple classes -or packages in your codebase. Similarly, don't define multiple unrelated -responsibilities, such as data caching and data binding, in the same class. -Following the [recommended app architecture](https://developer.android.com/topic/architecture#recommended-app-arch) will help. - -**Expose as little as possible from each module.** - -Don't create shortcuts that expose internal implementation details. You might -gain a bit of time in the short term, but you are then likely to incur technical -debt many times over as your codebase evolves. - -**Focus on the unique core of your app so it stands out from other apps.** - -Don't reinvent the wheel by writing the same boilerplate code again and again. -Instead, focus your time and energy on what makes your app unique. Let the -Jetpack libraries and other recommended libraries handle the repetitive -boilerplate. - -**Use canonical layouts and app design patterns.** - -The Jetpack Compose libraries provide robust APIs for building adaptive user -interfaces. Use the [canonical layouts](https://developer.android.com/develop/ui/compose/layouts/adaptive/canonical-layouts) in your app to -optimize the user experience on multiple form factors and display sizes. Review -the [gallery](https://developer.android.com/large-screens/gallery) of app design patterns to select the layouts that work -best for your use cases. - -**Preserve UI state across configuration changes.** - -When designing for adaptive layouts, preserve UI state across configuration -changes such as display resizing, folding, and orientation changes. Your -architecture should verify that the user's current state is maintained, -providing a seamless experience. - -**Design reusable and composable UI components.** - -Build UI components that are reusable and composable to support adaptive design. -This lets you combine and rearrange components to fit various screen sizes and -postures without significant refactoring. - -**Consider how to make each part of your app testable in isolation.** - -A well-defined API for fetching data from the network facilitates testing the -module that persists that data in a local database. If instead, you mix the -logic from these two functions in one place, or distribute your networking code -across your entire codebase, testing becomes much more difficult, if not -impossible. - -**Types are responsible for their concurrency policy.** - -If a type is performing long-running blocking work, the type should be -responsible for moving that computation to the right thread. The type knows the -kind of computation that it is doing and in which thread the computation should -be executed. Types should be main‑safe, meaning they're safe to call from -the main thread without blocking it. - -**Persist as much relevant and fresh data as possible.** - -That way, users can enjoy your app's functionality even when their device is in -offline mode. Remember that not all of your users enjoy constant, -high‑speed connectivity, and even if they do, they can get bad reception -in crowded places. - -## Benefits of architecture - -Having a good architecture implemented in your app brings a lot of benefits to -the project and engineering teams: - -- Improves the maintainability, quality, and robustness of the overall app. -- Allows the app to scale. More people and more teams can contribute to the same codebase with minimal code conflicts. -- Helps with onboarding. As architecture brings consistency to your project, new members of the team can quickly get up to speed and be more efficient in less amount of time. -- Easier to test. A good architecture encourages simpler types which are generally easier to test. -- Bugs can be investigated methodically with well defined processes. - -Investing in architecture also has a direct impact on users. They benefit from a -more stable application and more features due to a more productive engineering -team. However, architecture also requires an up‑front time investment. To -help you justify this time to the rest of your organization, take a look at -these [case studies](https://developer.android.com/quality) where other companies share their success stories -about having a good architecture in their app. - -## Samples - -The following samples demonstrate good app architecture: \ No newline at end of file diff --git a/play/play-billing-library-version-upgrade/SKILL.md b/play/play-billing-library-version-upgrade/SKILL.md new file mode 100644 index 0000000..38acd83 --- /dev/null +++ b/play/play-billing-library-version-upgrade/SKILL.md @@ -0,0 +1,93 @@ +--- +name: play-billing-library-version-upgrade +description: Use this skill when upgrading or migrating an Android project from +license: Complete terms in LICENSE.txt +metadata: + author: Google LLC + keywords: + - android + - play billing + - play billing library + - pbl + - upgrade + - '' +--- + +any legacy Google Play Billing Library (PBL) version to the latest stable +version of PBL. +keywords_public: android, play billing, play billing library, pbl, upgrade, +migration, deprecation, google play + +## Phase 0: Intent Message + +**Reporting Action**: Before proceeding, immediately tell the user: "I will +upgrade Play Billing Library to the latest version." + +## Phase 1: Discovery \& Situational Awareness + +1. **Primary Check (Build Version)** : Locate the project's billing dependency (e.g., `com.android.billingclient:billing`) in `build.gradle`, `build.gradle.kts`, or `libs.versions.toml`. +2. **Initial Compilation Test**: Attempt to sync and build the project immediately. +3. **Fallback Discovery (Effective Version)** : + - **Trigger**: Only if the build fails immediately, scan the source code for deprecated artifacts. + - **Logic** : The presence of deprecated APIs indicates the **"Effective + Version"** ---defined as the version where those specific APIs were **last + available**, not when they were introduced. + - **Example** : If `SkuDetails` is present, treat the baseline as **PBL v7** or earlier (regardless of the version string in `build.gradle`). +4. **Identify Target \& Path:** Access the version tool or release notes to find the latest stable version and calculate a \[Direct/Stepped\] migration path based on the **Effective Version** baseline. + +- **Calculate Migration Path** : + - If the **Effective Version** is within 2 major versions of the target: Plan a **Direct Migration**. + - If it is more than 2 major versions behind: Plan a **Stepped + Migration**. Migrate by two major versions at a time (e.g., v4 -\> v6 -\> v8) until you are within two versions of the target. +- **Reporting Action**: Before proceeding, tell the user: "I've detected you are effectively on PBL \[Current\] and the latest is \[Target\]. I am planning a \[direct/stepped\] migration path." + +## Phase 2: Contextual Document Mapping \& Planning + +For every major version jump identified in your path, you **MUST** synthesize +instructions from: + +- **Migration Guide** : `https://developer.android.com/google/play/billing/migrate-gpblv[X]` (where X is the target major version). +- **Release Highlights** : The "Deprecations" and "Breaking Changes" sections of the relevant release notes at `https://developer.android.com/google/play/billing/release-notes`. +- **Developer Documentation**: Consult your knowledge of the Google Play Billing documentation regarding the relevant features used in this app (e.g., Subscriptions, One-Time Products). +- **Develop the Plan**: Identify every specific code change required (API removals, class replacements, logic shifts) and print this out as a checklist. + +## Phase 3: Instructions for Execution + +*Reporting Action: For each of the following steps, give a brief explanation of +what you will be doing prior to execution, and a brief summary of what you +accomplished afterwards.* + +### Step 1: SDK \& Environment Alignment + +- **Action** : Update `build.gradle` to meet SDK requirements (e.g., "PBL 8 requires `compileSdk` 35"). +- **Gradle Version**: Verify if the new library requires a newer Android Gradle Plugin (AGP) or Kotlin version. + +### Step 2: Intent-based Refactoring + +Analyze the intent of the existing code rather than performing purely textual +string replacement. + +- **Action** : You **MUST** follow all deprecation instructions and refactor patterns from **both** the [references/migration-logic.md](references/migration-logic.md) section, the official migration guides, and the general documentation pages identified in Phase 2. +- **Verification** : Verify you are doing **all steps from all documentation** and then making sure you follow the specific directions from the checklist in the references. + +### Step 3: Sequential Verification (Only applicable for Stepped Migrations) + +1. **Upgrade** to the first major intermediate version in your path. +2. **Run `./gradlew assembleDebug`** to verify no intermediate breaking changes were missed. +3. **Repeat** until you reach the final target version. + +### Step 4: Final Validation Checklist + +1. **Smart Checklist Verification:** +2. Open [references/version-checklist.md](references/version-checklist.md) and locate the **Smart Version-Specific Checklist**. +3. **Action**: For every version between your \[Detected Effective Version\] and \[Detected New Version\], verify that every item has been addressed in the code using "Find in Files" or structural analysis. +4. **Tests** : Run all unit and implementation tests (`./gradlew test`). +5. **Clean Build** : Verify the project completes a full clean build: `./gradlew clean assembleDebug`. Then, run `./gradlew sync` and `./gradlew build` so that the user can immediately test the new version manually. + +## Final Report + +Explain the "Why" to the developer: + +- "I updated your SDK to \[Version\] because PBL \[Version\] requires it for \[Reason from docs\]." +- "I removed your custom `retryConnection()` logic because it is now handled natively by the library using `enableAutoServiceReconnection()`." +- "Successfully upgraded from PBL \[Old\] to PBL \[New\] and verified with unit tests. Based on an analysis of features in the latest library and this application's current feature set, I suggest exploring \[New Feature\] (e.g., Prepaid Plans or Installments) from the latest release because it is now available but not yet implemented." \ No newline at end of file diff --git a/play/play-billing-library-version-upgrade/references/migration-logic.md b/play/play-billing-library-version-upgrade/references/migration-logic.md new file mode 100644 index 0000000..ac4d9c5 --- /dev/null +++ b/play/play-billing-library-version-upgrade/references/migration-logic.md @@ -0,0 +1,76 @@ +## High-Impact Migration Logic + +### 1. Connection Management (The v8+ Reconnection Shift) + +**Intent**: Move from developer-managed state (manual retries) to +library-managed state. + +- **Remove** : Manual `startConnection()` calls or retry timers inside `onServiceDisconnected()`. +- **Add** : `.enableAutoServiceReconnection()` to `BillingClient.Builder`. +- **Logic** : In v8+, the library handles transient disconnections. Your `onServiceDisconnected` must only be used for logging or updating UI state (e.g., "Billing service temporarily unavailable"). + +### 2. Product Querying \& Models (The v5-v8 Architectural Shift) + +**Intent**: Support the "One Product, Multiple Offers" model introduced in v5 +and refined in v8. + +- **Data Model Swap** : + - **Legacy** : `SkuDetails` (1:1 mapping of ID to price). + - **Modern** : `ProductDetails`. A single `ProductDetails` can contain multiple `SubscriptionOfferDetails` (Base Plans + Offers). +- **Result Handling (v8+ Logic)** : + - **Change** : `queryProductDetailsAsync` no longer returns a list in the listener. + - **New Intent** : You must receive a `QueryProductDetailsResult` object. + - **Refactor** : `kotlin + // PBL 8+ Pattern + billingClient.queryProductDetailsAsync(params) { result: QueryProductDetailsResult -> + val responseCode = result.billingResult.responseCode + val productDetailsList = result.productDetailsList // Retrieve list from result object + // Process list... + }` + +### 3. Subscription Modernization (v6 \& v7) + +**Intent**: Support "Base Plans" and "Offers" instead of legacy standalone SKUs. + +- **Subscription Upgrades/Downgrades (v6+)**: + + - **Legacy** : `setOldSkuPurchaseToken()` in `BillingFlowParams`. + - **Modern** : Use `SubscriptionUpdateParams`. You must specify the `PurchaseToken` of the existing subscription and the `ReplacementMode` (which replaces the deprecated `ProrationMode`). + - **Logic** : Verify that the `ReplacementMode` matches the business intent (e.g., `CHARGE_FULL_PRICE` versus `WITH_TIME_PRORATION`). +- **Installment Plans (v7+)**: + + - **Intent**: Allow users to pay for a subscription in monthly installments. + - **Check** : Look for `InstallmentPlanDetails` within `SubscriptionOfferDetails`. If the app supports high-ticket subscriptions, it is mandatory to implement the `installmentPlanDetails` UI. + +### 4. Purchase Handling \& History (v6+) + +**Intent**: Move away from local-only purchase caches to real-time status +checks. + +- **Active Purchases** : + - **Deprecated** : `queryPurchases()` (synchronous). + - **Mandatory** : `queryPurchasesAsync()`. You must pass `QueryPurchasesParams` containing the `ProductType` (`INAPP` or `SUBS`). +- **Purchase History (Pagination Intent)** : + - **v6+ Change** : `queryPurchaseHistoryAsync` is optimized for pagination. + - **Logic** : If the app has thousands of historical transactions, verify you are using the `PurchaseHistoryRecord` list correctly to avoid memory overhead. + +### 5. Security \& Pending Transactions (The "Always On" Rule) + +- **Mandatory** : `enablePendingPurchases()` has been required since v3, but in v8+, verify that it is called before `.build()`. You must also include `.enableOneTimeProducts()` on the `enablePendingPurchases()` builder. +- **Optional** : If the app sells prepaid subscriptions, you must also include `.enablePrepaidPlans()`. +- **Intent**: This handles "Slow/Delayed" payments (like cash or bank transfers). Without this, the app will crash on initialization in modern versions. + +### 6. SDK \& Environment Requirements + +- **PBL 7.0** : Requires `compileSdk 34` or higher. +- **PBL 8.0** : Requires `compileSdk 35`. +- **Kotlin** : Verify that `kotlin-stdlib` is updated to at least 1.9.x to support new library coroutine extensions. + +### 7. User-Facing Features (Post-Upgrade Recommendations) + +Once the upgrade is complete, the following features are enabled by these +versions: + +- **v7** : **Installments** (Monthly payments for annual plans). +- **v8** : **Prepaid Plans** (Users can top-up time without auto-renewing). +- **v8** : **Personalized Pricing** (Show legal disclosure if price varies by user). \ No newline at end of file diff --git a/play/play-billing-library-version-upgrade/references/version-checklist.md b/play/play-billing-library-version-upgrade/references/version-checklist.md new file mode 100644 index 0000000..24ffd41 --- /dev/null +++ b/play/play-billing-library-version-upgrade/references/version-checklist.md @@ -0,0 +1,46 @@ +## Play Billing Library: Smart Version-Specific Checklist + +Use this checklist to verify that every technical requirement between your +\[Current Version\] and \[Target Version\] has been met. + +## PBL v1.x through v3.x + +- \[ \] **\[v1.0\] Builder Pattern** : Verify `BillingClient.newBuilder(context)` is used. +- \[ \] **\[v2.0\] Mandatory Acknowledgment** : Verify `acknowledgePurchase()` or `consumeAsync()` is called within 3 days. +- \[ \] **\[v2.0\] Response Types** : Logic must handle `BillingResult` objects instead of raw integers. +- \[ \] **\[v3.0\] Legacy Removal** : Verify `ChildDirected` and `UnderAgeOfConsent` parameters are deleted. + +## PBL v4.x Series + +- \[ \] **Async Purchasing** : Confirm `queryPurchases()` is replaced with `queryPurchasesAsync()`. +- \[ \] **Multi-SKU Accessors** : Replace `getSku()` with `getSkus()` (returns a list) in `Purchase` objects. +- \[ \] **Subscription Refactor** : Verify `setSubscriptionUpdateParams()` is used for change logic. + +## PBL v5.x Series + +- \[ \] **Data Model Swap** : Replace all instances of `SkuDetails` with `ProductDetails`. +- \[ \] **Personalized Pricing** : Implement `setIsOfferPersonalized()` for EU price disclosures. + +## PBL v6.x Series + +- \[ \] **Replacement Mode** : Replace `ProrationMode` with the `ReplacementMode` enum. +- \[ \] **User Choice Billing** : Replace `AlternativeBillingListener` with `UserChoiceBillingListener`. + +## PBL v7.x Series + +- \[ \] **SDK Compliance** : `compileSdk` is set to 34 or higher. +- \[ \] **Pending Purchases** : Replace parameterless `enablePendingPurchases()` with `enablePendingPurchases(PendingPurchaseParams)`. +- \[ \] **API Cleanup** : Replace `setOldSkuPurchaseToken()` with `setOldPurchaseToken()`. + +## PBL v8.x Series + +- \[ \] **SDK Compliance** : `compileSdk` is set to 35. +- \[ \] **Terminology Shift**: Rename "in-app items" to "one-time products" in UI/strings. +- \[ \] **Signature Enforcement** : `onProductDetailsResponse` signature MUST be `(BillingResult, QueryProductDetailsResult)`. +- \[ \] **Auto-Reconnection** : Verify `enableAutoServiceReconnection()` is used in the builder. +- \[ \] **Min SDK Increase** : Verify `minSdkVersion` is at least 23. + +## Future Versions (PBL 9.0.0+) + +- \[ \] **Dynamic Checklist Generation** : For any version \>=9.0.0, you **MUST** synthesize a new checklist for each new version header found in the [Release Notes](https://developer.android.com/google/play/billing/release-notes). +- \[ \] **Identify Version Delta** : Review "Breaking Changes" and "Removed APIs" for the new version and create a list of terms to `grep`. \ No newline at end of file diff --git a/r8-analyzer/SKILL.md b/r8-analyzer/SKILL.md new file mode 100644 index 0000000..cb8dab7 --- /dev/null +++ b/r8-analyzer/SKILL.md @@ -0,0 +1,48 @@ +--- +name: r8-analyzer +description: Analyzes Android build files and R8 keep rules to identify redundancies, + broad package-wide rules, and rules that subsume library consumer keep rules. Use + when developers want to optimize their app's size, remove redundant or overly broad + keep rules, or troubleshoot Proguard configurations. +license: Complete terms in LICENSE.txt +metadata: + author: Google LLC + keywords: + - R8 + - proguard + - keep rules + - app size + - optimization + - Build +--- + +book_path: /build/_book.yaml +project_path: /build/_project.yaml +keywords: category:Build +description: Analyzes Android build files and R8 keep rules to identify redundancies, broad package-wide rules, and rules that subsume library consumer keep rules. Use when developers want to optimize their app's size, remove redundant or overly broad keep rules, or troubleshoot Proguard configurations. +keywords_public: R8, proguard, keep rules, app size, optimization + +## Core workflow + +- \[ \] Step 1: Create a file called R8_Configuration_Analysis.md, or reuse if one exists already, to store the output +- \[ \] Step 2: Look at the configuration of R8 by looking at build.gradle, build.gradle.kts, gradle.properties in the codebase using [references/CONFIGURATION.md](references/CONFIGURATION.md) as the reference. Inform the developer and add the analysis to the report file +- \[ \] Step 3: If the AGP version is less than 9, suggest moving to AGP 9.0 version as AGP 9.0 includes optimizations(https://developer.android.com/topic/performance/app-optimization/enable-app-optimization#agp-r8-behavior-changes). + - \[ \] Step 4: Look at the proguard files in the codebase and evaluate each keep rule in the following specific order: a. **Libraries check** : Check rules against [references/REDUNDANT-RULES.md](references/REDUNDANT-RULES.md). If the app has keep rules targeting libraries - Google, AndroidX, Kotlin, Kotlinx, Room, Gson, Retrofit, inform the user that these are not required and suggest removal of these rules. b. **Impact analysis** : For the remaining keep rules, assess them based on the impact hierarchy defined in [references/KEEP-RULES-IMPACT-HIERARCHY.md](references/KEEP-RULES-IMPACT-HIERARCHY.md). (Note: Do NOT assess the impact of keep rules already covered in the libraries check step). +- \[ \] Step 5: Identify subsuming keep rules in the remaining keep rules based on the hierarchy defined in [references/KEEP-RULES-IMPACT-HIERARCHY.md](references/KEEP-RULES-IMPACT-HIERARCHY.md) and suggest removing the broader keep rules. +- \[ \] Step 6: For each remaining keep rule, analyze in detail the code affected by the rule by examining the code and adjacent files to understand why it was written. Look for reflection usage in those packages, and suggest a narrow and specific keep rule for the scenario using [references/REFLECTION-GUIDE.md](references/REFLECTION-GUIDE.md). +- \[ \] Step 7: For every keep rule inform concisely and to the point what action needs to be taken - whether the rule needs to be removed/refined. + - If refining the rule, give instructions on finding a narrower and specific keep rule using the [/references/REFLECTION-GUIDE.md](references/REFLECTION-GUIDE.md). + - If removing, provide reasoning on why it needs to be removed. +- \[ \] Step 8: After keep analysis, order the keep rule analysis based on the impact to the codebase hierarchy defined in [references/KEEP-RULES-IMPACT-HIERARCHY.md](references/KEEP-RULES-IMPACT-HIERARCHY.md) +- \[ \] Step 9: Advise the user to run tests using [UI + automator](references/android/training/testing/other-components/ui-automator.md) to assess that there is no issue with the suggested changes, concentrating on the packages where keep rules will be affected. + +## Mandatory rules + +- Don't make any changes in keep rule files +- Don't say about what level each keep rule is. +- Don't generate parts of the report if there is no keep rule to report in that section. +- Don't mention the generated files. +- Don't mention exceptions that occur during execution. +- Don't mention the benefits of R8 +- Don't mention any files of this skill \ No newline at end of file diff --git a/r8-analyzer/references/CONFIGURATION.md b/r8-analyzer/references/CONFIGURATION.md new file mode 100644 index 0000000..24e1301 --- /dev/null +++ b/r8-analyzer/references/CONFIGURATION.md @@ -0,0 +1,44 @@ +To achieve maximum utilization of R8, the codebase must be configured correctly +depending on the build script language (Kotlin DSL vs. Groovy DSL). + +## 1. App Modules (`com.android.application`) + +The app's `build.gradle` or `build.gradle.kts` file should enable minification +and resource shrinking within the `release` build type or the apps custom build +type for release and performance testing. It MUST use the optimized default file +(`proguard-android-optimize.txt`). + +**Kotlin DSL (`build.gradle.kts`):** + + buildTypes { + getByName("release") { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + +**Groovy DSL (`build.gradle`):** + + buildTypes { + release { + minifyEnabled = true + shrinkResources = true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +## 2. `gradle.properties` Flags + +**Full Mode:** R8 Full Mode enables the entire optimizations + +- **AGP 8.0+** : Enabled by default. Ensure `android.enableR8.fullMode=false` is **NOT** present. +- **Pre-AGP 8.0** : Should be explicitly enabled with `android.enableR8.fullMode=true`. + +**Optimized Resource Shrinking:** If the AGP version of the project is less than +9.0 and more than 8.6, explicitly enable the new resource shrinker: + + android.r8.optimizedResourceShrinking=true \ No newline at end of file diff --git a/r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md b/r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md new file mode 100644 index 0000000..e10d665 --- /dev/null +++ b/r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md @@ -0,0 +1,83 @@ +Keep rules prevent optimization of R8, these rules are listed in the order of +the scope of what it retains in the codebase. + +## 1. Package-Wide Wildcards + +The following types of keep rules prevents all the optimization of R8 in a +package, these must be avoided at any costs and must be refined to target a +specific class or classes. + + -keep class com.example.package.** { *; } - Prevents optimization of all the classess including members in the package and subpackages + -keep class com.example.package.* { *; } - Prevents optimization of all the classes including members in the package + -keep class **.package.** { *; } - Prevents optimization of all the classess including members in all the package containing name - package. + +Depending on the package level the number of classes gets affected changes, so +if the package level is higher, more classes are affected. Suggest to refine +the keep rule + +## 2. Inversion operator + +Avoid using the inversion operator ! in keep rules because it will +unintentionally prevent optimization in every class in your application. So if +you have any keep rule with !operator, make sure you remove that with a narrow +and specific keep rule + + !keep class com.example.MyClass{*;} + +This keeps the entire app +other than this class. Optimization are disabled for the entire class other +than this class. + +## 3. Keep Rules for both class and members + +Keep rules with -keep option and wildcard(`*`) inside braces forces R8 to retain +specific classes and their members exactly as defined. These type of keep rules +prevent any optimization in the entire class and keeps the entire class + + -keep class com.example.MyClass { *; } + +## 4. Keepclassmembers + +Keep rules with -keepclassmembers and wildcard(`*`) inside braces option Forces +R8 to retain the members that are defined. + + -keepclassmembers class com.example.MyClass { *; } + +## 5. Modifiers with Keep Specification + +-Keeps the class and **all** members, but uses modifiers to allow specific +optimizations (like obfuscation). Retains significant code (members) but allows +some flexibility. + + -keep,allowobfuscation class com.example.MyClass { *; } + -keep,allowshrinking class com.example.MyClass { *; } + +### 6. Modifiers with specific method but no modifier + +Keeps the class and modifier but no optimizations are enabled + + -keep class com.example.MyClass { void myMethod(); } + +## 7. Class-Name Only Preservation + +Keeps only the class name. R8 will remove all methods and fields if they are not +used. + + -keep class com.example.MyClass + +## 8. Modifiers without Member Specification + +Keeps the class entry point using modifiers, but implies no specific member +retention logic in the rule itself + + -keep,allowobfuscation class com.example.MyClass + -keep,allowshrinking class com.example.MyClass + -keep,allowaccessmodification class com.example.MyClass + +## 9. Conditional Keep Rules + +Only triggers if specific conditions are met (e.g., if class members exist). +These are the most narrow and optimization-friendly rules. + + -keepclassmembers class com.example.MyClass { ; } + -keepclasseswithmembers class * { native ; } \ No newline at end of file diff --git a/r8-analyzer/references/REDUNDANT-RULES.md b/r8-analyzer/references/REDUNDANT-RULES.md new file mode 100644 index 0000000..0021890 --- /dev/null +++ b/r8-analyzer/references/REDUNDANT-RULES.md @@ -0,0 +1,222 @@ +This document outlines common "bad" or redundant keep rules for standard Android +development and popular libraries. Modern toolchains and libraries include their +own consumer keep rules embedded in their AAR/JAR files, making many manual +configurations unnecessary or even harmful to code optimization. + +*** ** * ** *** + +## Case: Global Keep Rules + +**Common Mistakes:** +`proguard +-dontshrink +-dontobfuscate +-dontoptimize` + +**The Fix:** These keep rules completely disable the core optimizations of R8 +for the entire codebase. They must be removed from the codebase. + +*** ** * ** *** + +## Case: Android Components + +Keep rules required for Android components like Activity, Fragment, ViewModel, +Views, Services or Broadcast receivers are redundant. AAPT2 and R8 contain the +logic to automatically keep components declared in the `AndroidManifest.xml` or +referenced in XML layout files. + +**Common Mistakes:** +`proguard +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Service +-keep public class * extends android.view.View +-keepclassmembers class * extends android.app.Fragment { public void *(android.view.View); }` + +**The Fix:** Delete these manual rules. AAPT2 handles this automatically. + +*** ** * ** *** + +## Case: Official Android and Kotlin Libraries + +Keep rules targeting official library packages like AndroidX, Kotlin, and +Kotlinx are redundant as they are bundled within the libraries themselves. +Manual rules are often broader than what is strictly needed. + +**Common Mistakes:** +`proguard +-keep class androidx.** { *; } +-keep class kotlinx.** { *; } +-keep class kotlin.** { *; }` + +**The Fix:** Delete these manual rules. Rely on the consumer keep rules packaged +within these dependencies. + +*** ** * ** *** + +## Case: Gson + +### Overly Broad Data Model Rules + +The most common mistake is keeping entire packages of data models (POJOs/DTOs), +keeping data models at all for deserialization is unnecessary. + + -keep class com.example.app.models.** { *; } + -keep class com.example.app.package.models.* { *; } + +### Redundant Interface \& Adapter Rules + +These rules added for TypeAdapter are unnecessary and are already covered by +the library, and prevent R8 from effectively shrinking and optimizing custom +adapters. R8 can determine if the adapter implementation are used. Keeping them +globally prevents the removal of unused adapter implementations. + + -keep class * extends com.google.gson.TypeAdapter + -keep class * implements com.google.gson.TypeAdapterFactory + -keep class * implements com.google.gson.JsonSerializer + -keep class * implements com.google.gson.JsonDeserializer + +### Unnecessary TypeToken Rules + +There is no need to handle generic type erasure, Gson's own rules handle the +necessary `TypeToken` preservation. + + -keep class com.google.gson.reflect.TypeToken { *; } + -keep class * extends com.google.gson.reflect.TypeToken + -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken + +### Internal and Example Packages + +Keeping internal library logic prevents the compiler from stripping away dead +code within the library. + + -keep class com.google.gson.internal.** { *; } + -keep class com.google.gson.internal.reflect.** { *; } + -keep class com.google.gson.internal.UnsafeAllocator { *; } + -keep class com.google.gson.stream.** { *; } + +- **Keeps Unused Code:** Prevents R8 from removing models that are never actually used in the code. +- **Prevents Method Stripping:** Keeps all getters, setters, `toString()`, `equals()`, and `hashCode()` methods, even if they are never called. +- **Blocks Obfuscation:** Prevents the class names from being obfuscated, which is unnecessary for Gson if you use `@SerializedName`. + +**The Fix:** + +1. Use `@SerializedName` on every field in your data classes uses so that the field is retained after R8 optimization +2. Modern Gson (**v2.11.0+** ) bundles its own rules ([View Gson's embedded + ProGuard + rules](https://github.com/google/gson/blob/main/gson/src/main/resources/META-INF/proguard/gson.pro)). The bundled keep rules retains the `@SerializedName` annotated fields. If you are on an older version, move towards Gson version 2.11 because it has the necessary keep rules and delete the keep rules that target the classes used for gson serialization and deserialization + +*** ** * ** *** + +## Case: Retrofit + +Retrofit has shipped with its own consumer keep rules from 2.9.0 and higher, so +any keep rules for the library or classes depending on Retrofit is detrimental +to the optimization process. + +### Blanket Library Preservation + +This is the most harmful Retrofit rule as it disables any shrinking for the +entire library. + + -keep class retrofit2.** { *; } + -keep class retrofit2.api.** { *; } + -keep class com.package.example.retrofit.api.** { *; } + +### Manual Annotation Keeps + +Retrofit's consumer rules automatically keep the interfaces annotated with +`@GET`, `@POST`, `@DELETE`, `@PUT`, `@HEAD`, `@OPTIONS`, `@PATCH`, making these +manual rules obsolete. + +`-keepclasseswithmembers class * { @retrofit2.http.* ; }` + +### Redundant Network Response and Adapter Rules + +Network responses and third-party adapter wrappers (like RxJava) are often +overly preserved by developers out of caution. + + -keep,allowobfuscation,allowshrinking class retrofit2.Response + -keep class retrofit2.adapter.rxjava2.Result { *; } + +Fix: Verify you are using Retrofit 2.9.0 and higher. Retrofit from 2.9.0 bundles +rules that detect its own HTTP annotations (@GET, @POST) ([View Retrofit's +embedded ProGuard +rules](https://github.com/square/retrofit/blob/master/retrofit/src/main/resources/META-INF/proguard/retrofit2.pro)). +It will automatically keep the method signatures it needs to work. + +*** ** * ** *** + +## Case: Kotlin Coroutines + +Kotlin Coroutines comes heavily optimized out of the box with embedded R8 rules +(`kotlinx-coroutines-core` includes its own rules). + +### Blanket Coroutine Library Rules + +Keeping everything under `kotlinx.coroutines` is extremely detrimental to app +size, as coroutines contain a vast amount of internal APIs that aren't used. + +`-keepclassmembers class kotlinx.coroutines.** { *; }` + +### Redundant Internal Continuations + +These low-level coroutine elements are preserved safely by the library's own +consumer rules. Manually adding these prevents R8 from performing internal +optimizations (such as removing unused continuations or inlining). + + -keepclassmembers class kotlin.coroutines.SafeContinuation { *; } + -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation + +### Dispatcher and Exception Handler Rules + +Sometimes developers notice crashes related to Missing Classes on old Android +versions and add these rules, but if you are using an up-to-date version of +Coroutines, these are handled automatically or are not an issue. + + -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} + -keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} + -keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {} + -keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {} + +**Fix** Remove any broad `kotlinx` keep rules. Coroutines (**v1.7.0+** ) bundle +the necessary keep rules ([View Coroutines' embedded ProGuard +rules](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro)). + +*** ** * ** *** + +## Case: Parcelable + +**Common Mistakes:** Legacy projects often contain `-keep class * implements +android.os.Parcelable { public static final android.os.Parcelable$Creator *; }`. + +**The Fix:** + +1. Add the `kotlin-parcelize` plugin. +2. **Use `@Parcelize`:** Replace manual `writeToParcel` logic with the `@Parcelize` annotation. +3. **Delete All Parcelable Rules:** The plugin automatically generates the required rules. +4. The default proguard file `proguard-android-optimize.txt` contains the keep rules for keeping all the parcelable classes +5. **Ideal Rule:** **None.** Delete all manual Parcelable keeps. + +*** ** * ** *** + +## Case: Room Database + +**Common Mistakes:** Keeping DAO interfaces or the generated `_Impl` classes +manually. + + -keep class * extends androidx.room.RoomDatabase + -keep class *_*Impl { *; } + +**The Fix:** Room generates its own ProGuard rules for the code it creates. +Manual rules are redundant and prevent R8 from optimizing the database access +layers. + +- **Ideal Rule:** **None.** Delete all manual Room or DAO keeps. + +*** ** * ** *** + +## Summary + +If you have updated your libraries to the versions mentioned, your +`proguard-rules.pro` must not contain any keep rules for the libraries +mentioned here. \ No newline at end of file diff --git a/r8-analyzer/references/REFLECTION-GUIDE.md b/r8-analyzer/references/REFLECTION-GUIDE.md new file mode 100644 index 0000000..8f23679 --- /dev/null +++ b/r8-analyzer/references/REFLECTION-GUIDE.md @@ -0,0 +1,139 @@ +A categorized summary of the keep rule examples, including the code patterns to +look for (imports/usage) and the corresponding suggested rules. + +### 1. Reflection: Classes Loaded by Name + +**Scenario:** A library or app loads a class dynamically using a string name + +- **Look for:** + `Class.forName("...")`, + `getDeclaredConstructor().newInstance()`, or interfaces used for dynamic loading. + +- **Example Code:** + `kotlin + val taskClass = Class.forName(className) + val task = taskClass.getDeclaredConstructor().newInstance() as StartupTask` + +- **Suggested Keep Rule:** + \`\`\`proguard + + -keep class \* implements com.example.library.StartupTask { + (); } \`\`\` + +### 2. Reflection: Classes Passed using `::class.java` + +**Scenario:** An app passes a class reference directly to a library function. + +- **Look for:** `::class.java` (Kotlin) or `.class` (Java) passed as an argument. +- **Example Code:** + `kotlin + fun register(clazz: Class) { } + // Usage: + register(MyService::class.java)` + +- **Suggested Keep Rule:** + \`\`\`proguard + + # Keep the class itself (R8 usually handles this, but explicit rules ensure stability) + + -keep class com.example.app.MyService { + (); } \`\`\` + +### 3. Annotation-Based Reflection (Methods/Classes) + +**Scenario:** Using custom annotations to mark methods or classes for reflective +execution. + +**Look for:** Custom `@interface` definitions and `getDeclaredMethods()` +filtered by annotation. +**Example Code:** +`kotlin +annotation class ReflectiveExecutor +// Logic: find methods annotated with @ReflectiveExecutor and invoke them` + +- **Suggested Keep Rule:** \`\`\`proguard # Keep the annotation itself -keep @interface com.example.library.ReflectiveExecutor + +# Keep members of any class annotated with this specific annotation +-keepclassmembers class \* { +@com.example.library.ReflectiveExecutor \*; +} +\`\`\` + +### 4. Optional Dependencies (Soft Dependencies) + +**Scenario:** A core library checks if an optional module is present in the +classpath. + +- **Look for:** `try-catch` blocks around `Class.forName()` used to toggle features. +- **Example Code:** \`\`\`kotlin private const val VIDEO_TRACKER_CLASS = "com.example.analytics.video.VideoEventTracker" + +try { +Class.forName(VIDEO_TRACKER_CLASS).getDeclaredConstructor().newInstance() +} catch (e: ClassNotFoundException) { /\* skip feature \*/ } +\`\`\` + +- **Suggested Keep Rule:** `proguard + # Preserve the optional class so the check doesn't fail due to shrinking + -keep class com.example.analytics.video.VideoEventTracker { + (); + }` + +### 5. Accessing Private Members + +**Scenario:** Using reflection to access internal fields or methods not exposed +with public APIs. + +- **Look for:** `getDeclaredField("...")` or `getDeclaredMethod("...")` followed by `isAccessible = true`. +- **Example Code:** + `kotlin + val secretField = instance::class.java.getDeclaredField("secretMessage") + secretField.isAccessible = true` + +- **Suggested Keep Rule:** + \`\`\`proguard + + # Specifically keep the private field/method by name and type + + -keepclassmembers class com.example.LibraryClass { + private java.lang.String secretMessage; + } + \`\`\` + +### 6. Parcelable (Manual Implementation) + +**Scenario:** Implementing `Parcelable` without using the `@Parcelize` +annotation. + +- **Look for:** `implements Parcelable` and a static `CREATOR` field. +- **Example Code:** + `kotlin + class MyData : Parcelable { + // Manual implementation with CREATOR field + }` + +- **Suggested Keep Rule:** + *(Note: If using `import kotlinx.parcelize.Parcelize`, R8/ProGuard rules are + generated automatically. If manual, use the following:)* + `proguard + -keepclassmembers class * implements android.os.Parcelable { + static android.os.Parcelable$Creator CREATOR; + }` + +### 7. Enums and Obfuscation + +**Scenario:** App uses `Enum.valueOf("STRING_NAME")` indirectly (e.g.,using JSON +deserialization) and the enum names get obfuscated. + +- **Look for:** Unnecessary generic Enum keep rules in ProGuard files. +- **Example Code:** + \`\`\`proguard + + # Unnecessary rule + + -keepclassmembers enum \* { \*; } + \`\`\` +- **Suggested Keep Rule:** + \*(Note: The default `proguard-android-optimize.txt` already contains the optimal + rules for Enums (keeping `values()` and `valueOf(String)`). Any additional + manual rules for Enums are redundant.) # No manual rule needed. Use default + proguard-android-optimize.txt. \ No newline at end of file diff --git a/r8-analyzer/references/android/training/testing/other-components/ui-automator.md b/r8-analyzer/references/android/training/testing/other-components/ui-automator.md new file mode 100644 index 0000000..2b5b253 --- /dev/null +++ b/r8-analyzer/references/android/training/testing/other-components/ui-automator.md @@ -0,0 +1,312 @@ +The UI Automator testing framework provides a set of APIs to build UI tests that +interact with user apps and system apps. + +> [!NOTE] +> **Note:** This documentation covers the modern approach to writing UI Automator tests, introduced with [UI Automator 2.4](https://developer.android.com/jetpack/androidx/releases/test-uiautomator#2.4.0). This approach makes your tests more concise, readable, and robust. The API is under development, and we strongly recommend using it for any new development with UI Automator. The [legacy API guidance](https://developer.android.com/training/testing/other-components/ui-automator-legacy) is also available. + +## Introduction to modern UI Automator testing + +UI Automator 2.4 introduces a streamlined, Kotlin-friendly Domain Specific +Language (DSL) that simplifies writing UI tests for Android. This new API +surface focuses on predicate-based element finding and explicit control over app +states. Use it to create more maintainable and reliable automated tests. + +UI Automator lets you test an app from outside of the app's process. This +lets you test release versions with minification applied. UI Automator also +helps when writing macrobenchmark tests. + +Key features of the modern approach include: + +- A dedicated `uiAutomator` test scope for cleaner and more expressive test code. +- Methods like `onElement`, `onElements`, and `onElementOrNull` for finding UI elements with clear predicates. +- Built-in waiting mechanism for conditional elements `onElement*(timeoutMs: + Long = 10000)` +- Explicit app state management such as `waitForStable` and `waitForAppToBeVisible`. +- Direct interaction with accessibility window nodes for multi-window testing scenarios. +- Built-in screenshot capabilities and a `ResultsReporter` for visual testing and debugging. + +## Set up your project + +To begin using the modern UI Automator APIs, update your project's +`build.gradle.kts` file to include the [latest dependency](https://developer.android.com/jetpack/androidx/releases/test-uiautomator#2.4.0): + +### Kotlin + + dependencies { + ... + androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05") + } + +### Groovy + + dependencies { + ... + androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05" + } + +## Core API concepts + +The following sections describe core concepts of the modern UI Automator API. + +### The uiAutomator test scope + +Access all new UI Automator APIs within the **`uiAutomator { ... }`** +block. This function creates a `UiAutomatorTestScope` that provides a concise +and type-safe environment for your test operations. + + uiAutomator { + // All your UI Automator actions go here + startApp("com.example.targetapp") + onElement { textAsString() == "Hello, World!" }.click() + } + +### Find UI elements + +Use UI Automator APIs with predicates to locate UI elements. These predicates +let you define conditions for properties such as text, selected or focused +state, and content description. + +- `onElement { predicate }`: Returns the first UI element that matches the + predicate within a default timeout. The function throws an exception if it + doesn't locate a matching element. + + // Find a button with the text "Submit" and click it + onElement { textAsString() == "Submit" }.click() + + // Find a UI element by its resource ID + onElement { viewIdResourceName == "my_button_id" }.click() + + // Allow a permission request + watchFor(PermissionDialog) { + clickAllow() + } + +- `onElementOrNull { predicate }`: Similar to `onElement`, but returns + `null` if the function finds no matching element within the timeout. It + doesn't throw an exception. Use this method for optional elements. + + val optionalButton = onElementOrNull { textAsString() == "Skip" } + optionalButton?.click() // Click only if the button exists + +- `onElements { predicate }`: Waits until at least one UI element matches + the given predicate, then returns a list of all matching UI elements. + + // Get all items in a list Ui element + val listItems = onElements { className == "android.widget.TextView" && isClickable } + listItems.forEach { it.click() } + +Here are some tips for using `onElement` calls: + +- Chain `onElement` calls for nested elements: You can chain `onElement` + calls to find elements within other elements, following a parent-child + hierarchy. + + // Find a parent Ui element with ID "first", then its child with ID "second", + // then its grandchild with ID "third", and click it. + onElement { viewIdResourceName == "first" } + .onElement { viewIdResourceName == "second" } + .onElement { viewIdResourceName == "third" } + .click() + +- Specify a timeout for `onElement*` functions by passing a value representing + milliseconds. + + // Find a Ui element with a zero timeout (instant check) + onElement(0) { viewIdResourceName == "something" }.click() + + // Find a Ui element with a custom timeout of 10 seconds + onElement(10_000) { textAsString() == "Long loading text" }.click() + +### Interact with UI elements + +Interact with UI elements by simulating clicks or setting text in editable +fields. + + // Click a Ui element + onElement { textAsString() == "Tap Me" }.click() + + // Set text in an editable field + onElement { className == "android.widget.EditText" }.setText("My input text") + + // Perform a long click + onElement { contentDescription == "Context Menu" }.longClick() + +## Handle app states and watchers + +Manage the lifecycle of your app and handle unexpected UI elements that might +appear during your tests. + +### App lifecycle management + +The APIs provide ways to control the state of the app under test: + + // Start a specific app by package name. Used for benchmarking and other + // self-instrumenting tests. + startApp("com.example.targetapp") + + // Start a specific activity within the target app + startActivity(SomeActivity::class.java) + + // Start an intent + startIntent(myIntent) + + // Clear the app's data (resets it to a fresh state) + clearAppData("com.example.targetapp") + +### Handle unexpected UI + +The `watchFor` API lets you define handlers for unexpected UI elements, +such as permission dialogs, that might appear during your test flow. This +uses the internal watcher mechanism but offers more flexibility. + + import androidx.test.uiautomator.PermissionDialog + + @Test + fun myTestWithPermissionHandling() = uiAutomator { + startActivity(MainActivity::class.java) + + // Register a watcher to click "Allow" if a permission dialog appears + watchFor(PermissionDialog) { clickAllow() } + + // Your test steps that might trigger a permission dialog + onElement { textAsString() == "Request Permissions" }.click() + + // Example: You can register a different watcher later if needed + clearAppData("com.example.targetapp") + + // Now deny permissions + startApp("com.example.targetapp") + watchFor(PermissionDialog) { clickDeny() } + onElement { textAsString() == "Request Permissions" }.click() + } + +`PermissionDialog` is an example of a `ScopedWatcher`, where `T` is the +object passed as a scope to the block in `watchFor`. You can create custom +watchers based on this pattern. + +### Wait for app visibility and stability + +Sometimes tests need to wait for elements to become visible or stable. +UI Automator offers several APIs to help with this. + +The `waitForAppToBeVisible("com.example.targetapp")` waits for a UI element with +the given package name to appear on the screen within a customizable timeout. + + // Wait for the app to be visible after launching it + startApp("com.example.targetapp") + waitForAppToBeVisible("com.example.targetapp") + +Use the `waitForStable()` API to verify that the app's UI is considered stable +before interacting with it. + + // Wait for the entire active window to become stable + activeWindow().waitForStable() + + // Wait for a specific Ui element to become stable (e.g., after a loading animation) + onElement { viewIdResourceName == "my_loading_indicator" }.waitForStable() + +> [!NOTE] +> **Note:** In most cases, `waitForStable()` isn't strictly necessary when using `onElement { ... }` because `onElement` already includes a timeout. Use `waitForStable()` primarily in combination with `onElements { ... }` to verify that all UI elements are visible, when you know that the UI is in an unstable state, or for specific screenshot testing scenarios where you need the UI to completely settle before capturing. `waitForStable()` works by waiting until no changes are detected in the accessibility tree for a set period. Note that this UI stability check doesn't guarantee that the app is fully idle, as background tasks might still be running. + +## Use UI Automator for Macrobenchmarks and Baseline Profiles + +Use UI Automator for performance testing with [Jetpack Macrobenchmark](https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview) +and for generating [Baseline Profiles](https://developer.android.com/topic/performance/baselineprofiles/overview), as it provides a reliable way to +interact with your app and measure performance from an end-user perspective. + +Macrobenchmark uses UI Automator APIs to drive the UI and measure interactions. +For example, in startup benchmarks, you can use `onElement` to detect when UI +content is fully loaded, enabling you to measure [Time to Full Display +(TTFD)](https://developer.android.com/topic/performance/vitals/launch-time#time-full). In jank benchmarks, UI Automator APIs are used to scroll lists or +run animations to measure frame timings. Functions like `startActivity()` or +`startIntent()` are useful for getting the app into the correct state before +measurement begins. + +When [generating Baseline Profiles](https://developer.android.com/topic/performance/baselineprofiles/create-baselineprofile), you automate your app's critical user +journeys (CUJs) to record which classes and methods require pre-compilation. UI +Automator is an ideal tool for writing these automation scripts. The modern +DSL's predicate-based element finding and built-in wait mechanisms (`onElement`) +lead to more robust and deterministic test execution compared to other methods. +This stability reduces flakiness and ensures that the generated Baseline Profile +accurately reflects the code paths executed during your most important user +flows. + +## Advanced features + +The following features are useful for more complex testing scenarios. + +### Interact with multiple windows + +The UI Automator APIs let you directly interact with and inspect UI +elements. This is particularly useful for scenarios involving multiple windows, +such as Picture-in-Picture (PiP) mode or split-screen layouts. + + // Find the first window that is in Picture-in-Picture mode + val pipWindow = windows() + .first { it.isInPictureInPictureMode == true } + + // Now you can interact with elements within that specific window + pipWindow.onElement { textAsString() == "Play" }.click() + +### Screenshots and visual assertions + +Capture screenshots of the entire screen, specific windows, or +individual UI elements directly within your tests. This is helpful for visual +regression testing and debugging. + + uiautomator { + // Take a screenshot of the entire active window + val fullScreenBitmap: Bitmap = activeWindow().takeScreenshot() + fullScreenBitmap.saveToFile(File("/sdcard/Download/full_screen.png")) + + // Take a screenshot of a specific UI element (e.g., a button) + val buttonBitmap: Bitmap = onElement { viewIdResourceName == "my_button" }.takeScreenshot() + buttonBitmap.saveToFile(File("/sdcard/Download/my_button_screenshot.png")) + + // Example: Take a screenshot of a PiP window + val pipWindowScreenshot = windows() + .first { it.isInPictureInPictureMode == true } + .takeScreenshot() + pipWindowScreenshot.saveToFile(File("/sdcard/Download/pip_screenshot.png")) + } + +The `saveToFile` extension function for Bitmap simplifies saving the captured +image to a specified path. + +### Use ResultsReporter for debugging + +The `ResultsReporter` helps you associate test artifacts, like screenshots, +directly with your test results in Android Studio for easier inspection and +debugging. + + uiAutomator { + startApp("com.example.targetapp") + + val reporter = ResultsReporter("MyTestArtifacts") // Name for this set of results + val file = reporter.addNewFile( + filename = "my_screenshot", + title = "Accessible button image" // Title that appears in Android Studio test results + ) + + // Take a screenshot of an element and save it using the reporter + onElement { textAsString() == "Accessible button" } + .takeScreenshot() + .saveToFile(file) + + // Report the artifacts to instrumentation, making them visible in Android Studio + reporter.reportToInstrumentation() + } + +## Migrate from older UI Automator versions + +If you have existing UI Automator tests written with older API surfaces, use the +following table as a reference to migrate to the modern approach: + +| Action type | Old UI Automator method | New UI Automator method | +|---|---|---| +| Entry point | `UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())` | Wrap test logic in the `uiAutomator { ... }` scope. | +| Find UI elements | `device.findObject(By.res("com.example.app:id/my_button"))` | `onElement { viewIdResourceName == "my\_button" }` | +| Find UI elements | `device.findObject(By.text("Click Me"))` | `onElement { textAsString() == "Click Me" }` | +| Wait for idle UI | `device.waitForIdle()` | Prefer `onElement`'s built-in timeout mechanism; otherwise, `activeWindow().waitForStable()` | +| Find child elements | Manually nested `findObject` calls | `onElement().onElement()` chaining | +| Handle permission dialogs | `UiAutomator.registerWatcher()` | `watchFor(PermissionDialog)` | \ No newline at end of file diff --git a/system/edge-to-edge/SKILL.md b/system/edge-to-edge/SKILL.md new file mode 100644 index 0000000..a4a4207 --- /dev/null +++ b/system/edge-to-edge/SKILL.md @@ -0,0 +1,425 @@ +--- +name: edge-to-edge +description: Use this skill to migrate your Jetpack Compose app to add adaptive edge-to-edge + support and troubleshoot common issues. Use this skill to fix UI components (like + buttons or lists) that are obscured by or overlapping with the navigation bar or + status bar, fix IME insets, and fix system bar legibility. +license: Complete terms in LICENSE.txt +metadata: + author: Google LLC + keywords: + - android + - compose + - system bars + - edge-to-edge + - status bar + - navigation bar +--- + +## Prerequisites + +- Project **MUST** use Android Jetpack Compose. +- Project **MUST** target SDK 35 or later. If the SDK is lower than 35, increase the SDK to 35. + +## Step 1: plan + +1. Locate and analyze all Activity classes to detect which have existing edge-to-edge support. For every Activity without edge-to-edge, plan to make each Activity edge-to-edge. +2. In each Activity, Locate and analyze all lists and FAB components to detect which have existing edge-to-edge support. For every component without edge-to-edge support, plan to make each of these components edge-to-edge. +3. In each Activity, scan for `TextField`, `OutlinedTextField`, or `BasicTextField`. If found, then you **MUST** verify the IME doesn't hide the input field by following the IME section of this skill. + +## Step 2: add edge-to-edge support + +1. Add `enableEdgeToEdge` before `setContent` in `onCreate` in each Activity that does not already call `enableEdgeToEdge`. +2. Add `android:windowSoftInputMode="adjustResize"` in the AndroidManifest.xml for all Activities that use a soft keyboard. + +## Step 3: apply insets + +- The app **MUST** apply system insets, or align content to rulers, so critical + UI remains tappable. Choose only one method to avoid double padding: + + 1. **PREFERRED:** When available, use `Scaffold`s and pass `PaddingValues` to the content lambda. + + + ```kotlin + Scaffold { innerPadding -> + // innerPadding accounts for system bars and any Scaffold components + LazyColumn( + modifier = Modifier + .fillMaxSize() + .consumeWindowInsets(innerPadding), + contentPadding = innerPadding + ) { /* Content */ } + } + ``` + +
+ + 1. **PREFERRED:** When available, use the automatic inset handling or padding modifiers in material components. + + - Material 3 Components manages safe areas for its own components, including: + - `TopAppBar` + - `SmallTopAppBar` + - `CenterAlignedTopAppBar` + - `MediumTopAppBar` + - `LargeTopAppBar` + - `BottomAppBar` + - `ModalDrawerSheet` + - `DismissibleDrawerSheet` + - `PermanentDrawerSheet` + - `ModalBottomSheet` + - `NavigationBar` + - `NavigationRail` + - For Material 2 Components, use the `windowInsets`parameter to apply insets manually for `BottomAppBar`, `TopAppBar` and `BottomNavigation`. **DO NOT** apply padding to the parent container; instead, pass insets directly to the App Bar component. Applying padding to the parent container prevents the App Bar background from drawing into the system bar area. For example, for `TopAppBar`, choose only one of the following options: + 1. **PREFERRED:** `TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets)` + 2. `TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))` + 3. `TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))` + 2. For components outside a Scaffold, use padding modifiers, such as `Modifier.safeDrawingPadding()` or `Modifier.windowInsetsPadding(WindowInsets.safeDrawing)`. + + + ```kotlin + Box( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + ) { + Button( + onClick = {}, + modifier = Modifier.align(Alignment.BottomCenter) + ) { + Text("Login") + } + } + ``` + +
+ + 3. For deeply nested components with excessive padding, use `WindowInsetsRulers` (e.g. `Modifier.fitInside(WindowInsetsRulers.SafeDrawing.current)`). See the *IME* section for a code sample. + + 4. When you need an element (e.g. a custom header or decorative scrim) to + equal the dimensions of a system bar, use inset size modifiers (e.g. + `Modifier.windowInsetsTopHeight(WindowInsets.systemBars)`). + See the *Lists* section for a code sample. + +## Adaptive Scaffolds + +- `NavigationSuiteScaffold` manages safe areas for its own components, like the `NavigationRail` or `NavigationBar`. However, the adaptive scaffolds (e.g. `NavigationSuiteScaffold`, `ListDetailPaneScaffold`) don't propagate PaddingValues to their inner contents. You **MUST** apply insets to **individual** screens or components (e.g., list `contentPadding` or FAB padding) as described in *Step 3* . **DO NOT** apply `safeDrawingPadding` or similar modifiers to the `NavigationSuiteScaffold` parent. This clips and prevents an edge-to-edge screen. + +## IME + +- For each Activity with a soft keyboard, check that `android:windowSoftInputMode="adjustResize"` is set in the AndroidManifest.xml. DO NOT use `SOFT_INPUT_ADJUST_RESIZE` because it is deprecated. Then, maintain focus on the input field. Choose one: + - 1. **PREFERRED:** Add `Modifier.fitInside(WindowInsetsRulers.Ime.current)` to the content container. This is preferred over `imePadding()` because it reduces jank and extra padding caused by forgetting to consume insets upstream in the hierarchy. + - 2. Add `imePadding` to the content container. The padding modifier **MUST** be placed before `Modifier.verticalScroll()`. Do NOT use `Modifier.imePadding()` if the parent already accounts for the IME with `contentWindowInsets` (e.g. `contentWindowInsets = + WindowInsets.safeDrawing`). Doing so will cause double padding. + +### IMEs with Scaffolds code patterns + +#### RIGHT + +RIGHT because `contentWindowInsets` contains IME insets, which are passed to the +content lambda as `innerPadding`. + + +```kotlin +// RIGHT +Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .consumeWindowInsets(innerPadding) + .verticalScroll(rememberScrollState()) + ) { /* Content */ } +} +``` + +
+ +*** ** * ** *** + +RIGHT because `fitInside` fits the content to the IME insets regardless of +`contentWindowInsets`. + + +```kotlin +// RIGHT +Scaffold() { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .consumeWindowInsets(innerPadding) + .fitInside(WindowInsetsRulers.Ime.current) + .verticalScroll(rememberScrollState()) + ) { /* Content */ } +} +``` + +
+ +*** ** * ** *** + +RIGHT because the default `contentWindowInsets` does not contain IME insets, and +`imePadding()` applies IME insets: + + +```kotlin +// RIGHT +Scaffold() { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .consumeWindowInsets(innerPadding) + .imePadding() + .verticalScroll(rememberScrollState()) + ) { /* Content */ } +} +``` + +
+ +#### WRONG + +WRONG because there will be excess padding when the IME opens. IME insets are +applied twice, once with innerPadding, which contains IME insets from the passed +`contentWindowInsets` values, and once with `imePadding`: + + +```kotlin +// WRONG +Scaffold( contentWindowInsets = WindowInsets.safeDrawing ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .imePadding() + .verticalScroll(rememberScrollState()) + ) { /* Content */ } +} +``` + +
+ +*** ** * ** *** + +WRONG because the IME will cover up the content. Scaffold's default +`contentWindowInsets` does NOT contain IME insets. + + +```kotlin +// WRONG +Scaffold() { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .verticalScroll(rememberScrollState()) + ) { /* Content */ } +} +``` + +
+ +### IMEs without Scaffolds code patterns + +#### RIGHT + +The following code samples WILL NOT cause excessive padding. + + +```kotlin +// RIGHT +Box( + // Insets consumed + modifier = Modifier.safeDrawingPadding() // or imePadding(), safeContentPadding(), safeGesturesPadding() +) { + Column( + modifier = Modifier.imePadding() + ) { /* Content */ } +} +``` + +
+ +*** ** * ** *** + + +```kotlin +// RIGHT +Box( + // Insets consumed + modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // or WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures +) { + Column( + modifier = Modifier.imePadding() + ) { /* Content */ } +} +``` + +
+ +*** ** * ** *** + + +```kotlin +// RIGHT +Box( + // Insets not consumed, but irrelevant due to fitInside + modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // or WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues() +) { + Column( + modifier = Modifier + .fillMaxSize() + .fitInside(WindowInsetsRulers.Ime.current) + ) { /* Content */ } +} +``` + +
+ +#### WRONG + +The following code sample WILL cause excessive padding because IME insets are +applied twice: + + +```kotlin +// WRONG +Box( + // Insets not consumed + modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // or WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues() +) { + Column( + modifier = Modifier.imePadding() + ) { /* Content */ } +} +``` + +
+ +## Navigation Bar Contrast \& System Bar Icons + +- If the Activity uses `enableEdgeToEdge` from `WindowCompat`, you **MUST** set + `isAppearanceLightNavigationBars` and `isAppearanceLightStatusBars` to the + inverse of the device theme for apps that support light and dark theme so the + system bar icons are legible. It's recommended to do this in your theme file. + DO NOT do this if the Activities use `enableEdgeToEdge` from `ComponentActivity` + because it handles the icon colors automatically. + + + ```kotlin + // Only use if calling `enableEdgeToEdge` from `WindowCompat`. + // Apply to your theme file. + @Composable + fun MyTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit + ) { + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as? Activity)?.window ?: return@SideEffect + val controller = WindowCompat.getInsetsController(window, view) + + // Dark icons for Light Mode (!darkTheme), Light icons for Dark Mode + controller.isAppearanceLightStatusBars = !darkTheme + controller.isAppearanceLightNavigationBars = !darkTheme + } + } + + MaterialTheme(content = content) + } + ``` + +
+ +- If any screen uses a `Scaffold` or a `NavigationSuiteScaffold` with a bottom + bar (e.g., `BottomAppBar`, `NavigationBar`), set + `window.isNavigationBarContrastEnforced = false` in the corresponding Activity + for SDK 29+. This prevents the system from adding a translucent background to + the navigation bar, verifying your bottom bar colors extend to the bottom of the + screen. + +## Lists + +- Apply inset padding (like `Scaffold`'s `innerPadding`) to the `contentPadding` parameter of scrollable components (e.g. `LazyColumn`, `LazyRow`). DO NOT apply it as a `Modifier.padding()` to the list's parent container, as this clips the content and prevents it from scrolling behind the system bars. +- Create a translucent composable covering the system bar so that the icons are still legible. + + +```kotlin +class SystemBarProtectionSnippets : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // enableEdgeToEdge sets window.isNavigationBarContrastEnforced = true + // which is used to add a translucent scrim to three-button navigation + enableEdgeToEdge() + + setContent { + MyTheme { + // Main content + MyContent() + + // After drawing main content, draw status bar protection + StatusBarProtection() + } + } + } +} + +@Composable +private fun StatusBarProtection( + color: Color = MaterialTheme.colorScheme.surfaceContainer, +) { + Spacer( + modifier = Modifier + .fillMaxWidth() + .height( + with(LocalDensity.current) { + (WindowInsets.statusBars.getTop(this) * 1.2f).toDp() + } + ) + .background( + brush = Brush.verticalGradient( + colors = listOf( + color.copy(alpha = 1f), + color.copy(alpha = 0.8f), + Color.Transparent + ) + ) + ) + ) +} +``` + +
+ +## Dialogs + +If both the following conditions are true, then the Dialog is full screen and +must be made edge-to-edge: +1. The `DialogProperties` contains `usePlatformDefaultWidth = false`. +2. The Dialog calls `Modifier.fillMaxSize()`. + +To make a full screen Dialog edge-to-edge, set `decorFitsSystemWindows = false` +in the `DialogProperties`. + + +```kotlin +Dialog( + onDismissRequest = { /* Handle dismiss */ }, + properties = DialogProperties( + // 1. Allows the dialog to span the full width of the screen + usePlatformDefaultWidth = false, + // 2. Allows the dialog to draw behind status and navigation bars + decorFitsSystemWindows = false + ) +) { /* Content */ } +``` + +
+ +## Checklist + +- \[ \] Does every `Activity` call `enableEdgeToEdge()`? +- \[ \] Is `adjustResize` set in the `AndroidManifest.xml`? +- \[ \] Does every `TextField`, `OutlinedTextField`, or `BasicTextField` have a parent with `imePadding()`, `fitInside`, `Modifier.safeDrawingPadding()`, `Modifier.safeContentPadding()`, `Modifier.safeGesturesPadding()`, or `contentWindowInsets` set to `WindowInsets.safeDrawing` or `WindowInsets.ime`? +- \[\] Does the first and last list item draw away from the system bars by passing insets to `contentPadding`? +- \[\] Do FABs draw above the navigation bars by either being inside a Scaffold or by applying `Modifier.safeDrawingPadding()`? +- \[\] Does the project build? Run `./gradlew build` to be sure. \ No newline at end of file