diff --git a/docs/main/android/troubleshooting.md b/docs/main/android/troubleshooting.md index eceb0374d..aa2deb903 100644 --- a/docs/main/android/troubleshooting.md +++ b/docs/main/android/troubleshooting.md @@ -120,20 +120,59 @@ Also, if you are migrating from Capacitor 1 or 2, make sure you enabled the [aut If still getting the "Plugin not implemented" error, make sure you are not using service workers, that prevents Capacitor's and Plugins code from injecting. Or if you want to use them, you can use [this workaround](https://github.com/ionic-team/capacitor/issues/1655#issuecomment-579229390) for making the injection work. -## Using Proguard +## Using R8 -ProGuard is a tool used to shrink, obfuscate, and reduce the size of your app. It is enabled by setting the `minifyEnabled` option in `build.gradle` to `true`. This process can sometimes lead to issues in Capacitor when using a plugin or some custom native code that relies on its code being being readable at run time, such as code reflection. ProGuard scans code to try and optimize and shrink the size of an app and sometimes this process can remove classes or methods that are important for the functionality of a plugin. +R8 shrinks, optimizes and obfuscates your app's code when `minifyEnabled` is `true` in the release block of `app/build.gradle`. It's on by default in Capacitor 9 apps. To turn it off, set `minifyEnabled false`. -As of Capacitor v3.2.3 there are ProGuard rules included in Capacitor that cover the core functionality of Capacitor plugins, permissions, and activity results. If you are using an earlier version of Capacitor than v3.2.3, add [the following rules](https://github.com/ionic-team/capacitor/blob/main/android/capacitor/proguard-rules.pro) to your Android project's `proguard-rules.pro` file. Those rules should resolve problems with any of the core Capacitor features and core plugins. +Capacitor and the official plugins ship the rules they need, so most apps don't need rules of their own. R8 only runs in release builds, so test a release build before publishing. -If you still encounter any issues after adding those rules, try to identify the source plugin or native code and add a rule to cover the specific plugin code, for example: +### When something fails only in release + +R8 removes or renames code it can't see being used, such as classes looked up by name, code called from native code, and Gson models without `@SerializedName`. Find the class in the error and add a keep rule for it to `app/proguard-rules.pro`: ``` --keep class com.mythirdpartyplugin.** { *; } +-keep class com.example.MyModel { ; } ``` -If you are certain a Capacitor plugin is causing the ProGuard issue the following ProGuard rule will cover any plugin class code, if you don't mind all plugins being exempt from ProGuard processing: +Keep the rule as narrow as you can, since keeping a whole package stops R8 from optimizing all of it. If the class comes from a plugin, report it to the plugin's author so the plugin can ship the rule. + +If the release build fails with "Missing classes", add the rules R8 lists in `app/build/outputs/mapping/release/missing_rules.txt`. + +### Libraries with old keep rules + +AGP 9 no longer keeps a constructor just because its class is kept. Older versions of these libraries relied on that, so they fail at runtime under R8. If your app's own dependencies bring one of them, upgrade it or add its rule to `app/proguard-rules.pro`: + +| Library | Fixed in | Rule | +| :------ | :------- | :--- | +| Room | 2.7 | `-keep class * extends androidx.room.RoomDatabase { (); }` | +| WorkManager | 2.10 | `-keepclassmembers class * extends androidx.work.InputMerger { (); }` | +| firebase-components, used by Firebase and ML Kit | 18.0.1 | `-keep class * implements com.google.firebase.components.ComponentRegistrar { (); }` | + +If another library fails the same way, copy its class-only keep rule into `app/proguard-rules.pro` and add `{ (); }` to it. + +### Stack traces +R8 writes the map of renamed classes and methods to `app/build/outputs/mapping/release/mapping.txt` and adds it to your AAB, where Google Play reads it. The Crashlytics Gradle plugin uploads it too. To decode a stack trace yourself, use [retrace](https://developer.android.com/tools/retrace). + +### Resource shrinking + +`shrinkResources` stays off by default. Google Play's code optimization scores only measure your app's code, so resource shrinking barely changes them, though Play Console lists it as off. If you turn it on, keep the resources your app only reads by name, such as notification icons and sounds named in `capacitor.config.json`, with a file like `app/src/main/res/raw/keep.xml`: + +```xml + ``` --keep public class * extends com.getcapacitor.Plugin + +### Capacitor 8 + +Capacitor 8 apps have R8 off by default. To turn it on, use Capacitor 8.5.4 or later and also switch to `proguard-android-optimize.txt`, since `proguard-android.txt` turns off R8's optimizations: + +```diff +buildTypes { + release { +- minifyEnabled false +- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ++ minifyEnabled true ++ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } +} ``` diff --git a/docs/main/updating/9-0.md b/docs/main/updating/9-0.md index 1a8f537b2..86054d4c8 100644 --- a/docs/main/updating/9-0.md +++ b/docs/main/updating/9-0.md @@ -65,6 +65,8 @@ Whichever path you take, finish with the [iOS verification checklist](#verify-th Capacitor 9 also removes the Java APIs that were deprecated in previous major versions, most of them used by plugin authors — see [Breaking changes in code](/main/updating/plugins/9-0.md#android). If your app has custom native code, check that list too. +Release builds now run R8 by default, which can break code that R8 can't see being used. See [Turn on R8 in release builds](#turn-on-r8-in-release-builds) below. + ## Breaking changes in @capacitor/ios Capacitor 9 boots iOS apps from a SwiftUI `App` struct instead of a UIKit `AppDelegate`. Your app project gains `App.swift` and `CapacitorView.swift`, the `AppDelegate` is demoted to a `@UIApplicationDelegateAdaptor`, your `SceneDelegate` is emptied out, and `Main.storyboard` is deleted. Every app needs this change, including apps already on 8.5 — see [Adopt the SwiftUI entry point](#adopt-the-swiftui-entry-point) below. @@ -429,6 +431,22 @@ buildTypes { } ``` +### Turn on R8 in release builds + +New Capacitor 9 apps build their release with R8, which shrinks, optimizes and obfuscates the app's code, and `npx cap migrate` turns it on in existing apps. Google Play will [require code optimization](https://support.google.com/googleplay/android-developer/answer/17492799) for larger apps from February 2027. If `npx cap migrate` warns that it couldn't set `minifyEnabled`, set it in the release block of `app/build.gradle`: + +```diff +buildTypes { + release { +- minifyEnabled false ++ minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } +} +``` + +R8 only runs in release builds, so test a release build of your app after updating. If something fails only in release, see [Using R8](/main/android/troubleshooting.md#using-r8). To keep R8 off, set `minifyEnabled false`. + ### Declare dependency versions in your app's build.gradle Your `app/build.gradle` references version variables that are declared in the root project's `variables.gradle`, and relies on Gradle walking up the project hierarchy to resolve them. That implicit lookup is deprecated in Gradle 9.6 and becomes an error in Gradle 10, so declare them explicitly at the top of the file: diff --git a/docs/main/updating/plugins/9-0.md b/docs/main/updating/plugins/9-0.md index 02bc9305a..956ead2c9 100644 --- a/docs/main/updating/plugins/9-0.md +++ b/docs/main/updating/plugins/9-0.md @@ -230,6 +230,32 @@ buildTypes { } ``` +### Check your plugin with R8 + +Capacitor 9 apps run R8 in release builds by default, so plugin code that R8 can't see being used breaks in release. Check your plugin for: + +- Classes or methods looked up by name, or created or called from native code (JNI) +- Gson models without `@SerializedName`, Gson below 2.11, or library classes like `kotlin.Pair` serialized to JSON +- Dependencies that reference classes your plugin doesn't include, which fail the app's release build with "Missing classes". Add the `-dontwarn` lines R8 lists in the app's `build/outputs/mapping/release/missing_rules.txt`. +- Room below 2.7, WorkManager below 2.10, or firebase-components below 18.0.1, which need the rules in [Libraries with old keep rules](/main/android/troubleshooting.md#libraries-with-old-keep-rules) + +Put the rules in `android/proguard-rules.pro` and ship that file to apps. `@capacitor/plugin-migration-v8-to-v9` does this for you when the file exists: + +```diff +// android/build.gradle +defaultConfig { ++ consumerProguardFiles "proguard-rules.pro" +} +``` + +```diff +// package.json +"files": [ ++ "android/proguard-rules.pro", +``` + +Keep the rules narrow, since they apply to every app that uses your plugin, and leave out global options like `-dontobfuscate`, which fail the app's build under AGP 9. To test, build your example app's release with `minifyEnabled true`, call each plugin method, and check it behaves as in a debug build. + ### Remove the standalone Kotlin plugin AGP 9 bundles the Kotlin Gradle Plugin natively (Kotlin `2.2.10`) instead of requiring it to be applied separately. If your plugin still applies `kotlin-android` (or `org.jetbrains.kotlin.android`) and declares its own `kotlin_version`/`kotlin-stdlib`/`kotlin-gradle-plugin`, remove them, otherwise you'll hit a duplicate-plugin build failure: diff --git a/docs/plugins/cordova.md b/docs/plugins/cordova.md index be3f71f8e..0b892bef7 100644 --- a/docs/plugins/cordova.md +++ b/docs/plugins/cordova.md @@ -55,6 +55,19 @@ Capacitor does not support Cordova install variables, auto configuration, or hoo Consult the [iOS](/main/ios/configuration.md) and [Android](/main/android/configuration.md) configuration guides for info on how to configure each platform. +### R8 rules + +Capacitor already keeps Cordova plugin classes from R8. If a plugin needs more rules, for example for a class it loads by name, its Gradle file (the `` in `plugin.xml`) has to write them, since Cordova apps only get that file. Write them outside the build folder, so `clean` doesn't delete them before R8 runs: + +```groovy +def rules = new File(rootDir, '.gradle/my-plugin-rules.pro') +rules.parentFile.mkdirs() +rules.text = '-keep class com.example.LoadedByName { (); }\n' +android.buildTypes.release.proguardFile(rules) +``` + +This works in both Capacitor and Cordova apps. + ### Known Incompatible Plugins If a plugin is known to conflict or cause build issues, it will be skipped when running `npx cap sync`.