From 12a6289f089e36d4232194c4e2d9deb19ba3c00c Mon Sep 17 00:00:00 2001 From: Andrew de Waal Date: Tue, 3 Feb 2026 10:03:09 -0800 Subject: [PATCH] feat: Add support for Android build variants (feat #14777) --- crates/tauri-cli/config.schema.json | 10 ++++++++++ crates/tauri-cli/schema.json | 8 ++++++++ crates/tauri-cli/src/mobile/android/dev.rs | 14 +++++++++++--- crates/tauri-cli/src/mobile/android/run.rs | 15 ++++++++++++--- crates/tauri-cli/src/mobile/init.rs | 7 +++++++ crates/tauri-cli/tauri.config.schema.json | 9 ++++++++- .../templates/mobile/android/app/build.gradle.kts | 3 +++ .../schemas/config.schema.json | 10 ++++++++++ crates/tauri-utils/src/config.rs | 11 +++++++++++ 9 files changed, 80 insertions(+), 7 deletions(-) diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index b56437751..e7c88a382 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -85,6 +85,7 @@ "active": false, "android": { "autoIncrementVersionCode": false, + "debugApplicationIdSuffix": ".debug", "minSdkVersion": 24 }, "createUpdaterArtifacts": false, @@ -2284,6 +2285,7 @@ "description": "Android configuration.", "default": { "autoIncrementVersionCode": false, + "debugApplicationIdSuffix": ".debug", "minSdkVersion": 24 }, "allOf": [ @@ -3833,6 +3835,14 @@ "description": "Whether to automatically increment the `versionCode` on each build.\n\n - If `true`, the generator will try to read the last `versionCode` from\n `tauri.properties` and increment it by 1 for every build.\n - If `false` or not set, it falls back to `version_code` or semver-derived logic.\n\n Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.", "default": false, "type": "boolean" + }, + "debugApplicationIdSuffix": { + "description": "Application ID suffix to append for debug builds.\n This allows installing debug and release versions side-by-side on the same device.\n Example: \".debug\" will make debug builds use \"com.example.app.debug\" as the application ID.", + "default": ".debug", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/crates/tauri-cli/schema.json b/crates/tauri-cli/schema.json index 6bd2b3bc8..9d0113d91 100644 --- a/crates/tauri-cli/schema.json +++ b/crates/tauri-cli/schema.json @@ -3095,6 +3095,14 @@ "format": "uint32", "maximum": 2100000000.0, "minimum": 1.0 + }, + "debugApplicationIdSuffix": { + "description": "Application ID suffix to append for debug builds.\n This allows installing debug and release versions side-by-side on the same device.\n Example: \".debug\" will make debug builds use \"com.example.app.debug\" as the application ID.", + "default": ".debug", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/crates/tauri-cli/src/mobile/android/dev.rs b/crates/tauri-cli/src/mobile/android/dev.rs index 7387eb831..d9510ebc6 100644 --- a/crates/tauri-cli/src/mobile/android/dev.rs +++ b/crates/tauri-cli/src/mobile/android/dev.rs @@ -330,7 +330,7 @@ fn run_dev( if open { open_and_wait(config, &env) } else if let Some(device) = &device { - match run(device, options, config, &env, metadata, noise_level) { + match run(device, options, config, &env, metadata, noise_level, &*tauri_config) { Ok(c) => Ok(Box::new(c) as Box), Err(e) => { crate::dev::kill_before_dev_process(); @@ -352,6 +352,7 @@ fn run( env: &Env, metadata: &AndroidMetadata, noise_level: NoiseLevel, + tauri_config: &tauri_utils::config::Config, ) -> crate::Result { let profile = if options.debug { Profile::Debug @@ -361,8 +362,14 @@ fn run( let build_app_bundle = metadata.asset_packs().is_some(); + let application_id_suffix = if profile == Profile::Debug { + tauri_config.bundle.android.debug_application_id_suffix.clone() + } else { + None + }; + device - .run( + .run_with_application_id_suffix( config, env, noise_level, @@ -374,7 +381,8 @@ fn run( }), build_app_bundle, false, - ".MainActivity".into(), + format!("{}.MainActivity", config.app().identifier()), + application_id_suffix, ) .map(DevChild::new) .context("failed to run Android app") diff --git a/crates/tauri-cli/src/mobile/android/run.rs b/crates/tauri-cli/src/mobile/android/run.rs index c77596b03..42931f5b1 100644 --- a/crates/tauri-cli/src/mobile/android/run.rs +++ b/crates/tauri-cli/src/mobile/android/run.rs @@ -125,9 +125,17 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { if let Some(device) = device { let config = built_application.config.clone(); let release = options.release; - let runner = move |_tauri_config: &ConfigMetadata| { + + let runner = move |tauri_config: &ConfigMetadata| { + + let application_id_suffix = if !release { + tauri_config.bundle.android.debug_application_id_suffix.clone() + } else { + None + }; + device - .run( + .run_with_application_id_suffix( &config, &env, noise_level, @@ -143,7 +151,8 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { }), false, false, - ".MainActivity".into(), + format!("{}.MainActivity", config.app().identifier()), + application_id_suffix, ) .map(|c| Box::new(DevChild::new(c)) as Box) .context("failed to run Android app") diff --git a/crates/tauri-cli/src/mobile/init.rs b/crates/tauri-cli/src/mobile/init.rs index 7ace53b51..db7fa52f5 100644 --- a/crates/tauri-cli/src/mobile/init.rs +++ b/crates/tauri-cli/src/mobile/init.rs @@ -140,6 +140,13 @@ fn exec( let (config, metadata) = super::android::get_config(&app, &tauri_config, &[], &Default::default()); map.insert("android", &config); + + // Add application_id_suffix to the map for template access + // The template will access it via a helper or we'll modify template to use root context + if let Some(suffix) = &tauri_config.bundle.android.debug_application_id_suffix { + map.insert("android-debug-application-id-suffix", suffix); + } + super::android::project::gen( &config, &metadata, diff --git a/crates/tauri-cli/tauri.config.schema.json b/crates/tauri-cli/tauri.config.schema.json index b6c64b09b..ea575a222 100644 --- a/crates/tauri-cli/tauri.config.schema.json +++ b/crates/tauri-cli/tauri.config.schema.json @@ -3081,7 +3081,7 @@ "additionalProperties": false }, "AndroidConfig": { - "description": "General configuration for the iOS target.", + "description": "General configuration for the Android target.", "type": "object", "properties": { "minSdkVersion": { @@ -3100,6 +3100,13 @@ "format": "uint32", "maximum": 2100000000.0, "minimum": 1.0 + }, + "debugApplicationIdSuffix": { + "description": "Application ID suffix to append for debug builds.\n This allows installing debug and release versions side-by-side on the same device.\n Example: \".debug\" will make debug builds use \"com.example.app.debug\" as the application ID.", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/crates/tauri-cli/templates/mobile/android/app/build.gradle.kts b/crates/tauri-cli/templates/mobile/android/app/build.gradle.kts index 621f7bcae..67deaf8a0 100644 --- a/crates/tauri-cli/templates/mobile/android/app/build.gradle.kts +++ b/crates/tauri-cli/templates/mobile/android/app/build.gradle.kts @@ -28,6 +28,9 @@ android { } buildTypes { getByName("debug") { + {{#if android-debug-application-id-suffix}} + applicationIdSuffix = "{{android-debug-application-id-suffix}}" + {{/if}} manifestPlaceholders["usesCleartextTraffic"] = "true" isDebuggable = true isJniDebuggable = true diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index b56437751..e7c88a382 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -85,6 +85,7 @@ "active": false, "android": { "autoIncrementVersionCode": false, + "debugApplicationIdSuffix": ".debug", "minSdkVersion": 24 }, "createUpdaterArtifacts": false, @@ -2284,6 +2285,7 @@ "description": "Android configuration.", "default": { "autoIncrementVersionCode": false, + "debugApplicationIdSuffix": ".debug", "minSdkVersion": 24 }, "allOf": [ @@ -3833,6 +3835,14 @@ "description": "Whether to automatically increment the `versionCode` on each build.\n\n - If `true`, the generator will try to read the last `versionCode` from\n `tauri.properties` and increment it by 1 for every build.\n - If `false` or not set, it falls back to `version_code` or semver-derived logic.\n\n Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.", "default": false, "type": "boolean" + }, + "debugApplicationIdSuffix": { + "description": "Application ID suffix to append for debug builds.\n This allows installing debug and release versions side-by-side on the same device.\n Example: \".debug\" will make debug builds use \"com.example.app.debug\" as the application ID.", + "default": ".debug", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index 8826e3f51..f184094db 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -2919,6 +2919,12 @@ pub struct AndroidConfig { /// Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository. #[serde(alias = "auto-increment-version-code", default)] pub auto_increment_version_code: bool, + + /// Application ID suffix to append for debug builds. + /// This allows installing debug and release versions side-by-side on the same device. + /// Example: ".debug" will make debug builds use "com.example.app.debug" as the application ID. + #[serde(alias = "debugApplicationIdSuffix", default = "default_debug_application_id_suffix")] + pub debug_application_id_suffix: Option, } impl Default for AndroidConfig { @@ -2927,6 +2933,7 @@ impl Default for AndroidConfig { min_sdk_version: default_min_sdk_version(), version_code: None, auto_increment_version_code: false, + debug_application_id_suffix: default_debug_application_id_suffix(), } } } @@ -2935,6 +2942,10 @@ fn default_min_sdk_version() -> u32 { 24 } +fn default_debug_application_id_suffix() -> Option { + Some(".debug".to_string()) +} + /// Defines the URL or assets to embed in the application. #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(JsonSchema))]