From bfb0642a1d14e93c39713d284eaf6b6671e07d67 Mon Sep 17 00:00:00 2001 From: Fontlos <56759523+fontlos@users.noreply.github.com> Date: Wed, 25 Jun 2025 03:52:10 +0800 Subject: [PATCH] fix(cli, breaking): Align Android applicationId with user's `bundle.identifier` (#4103) * fix(cli, breaking): Align Android applicationId with user's `bundle.identifier` This commit fixes the Android `applicationId` generation to accurately reflect the user-configured `bundle.identifier` when provided. It also refactors the logic by removing the `full_mobile_app_name` function, with `bundle_identifier` now being the direct source for the Android `applicationId`. The `bundle_identifier` function now operates as follows: 1. If `bundle.identifier` is explicitly set by the user: - It validates that the identifier has at least three segments (e.g., `com.example.app`). - The user's `identifier` is then returned and used directly as the Android `applicationId`. 2. If `config.bundle.identifier` is not set by the user: - It defaults to `com.example.{PascalCaseExecutableName}`. The `executable_name` (e.g., "my-app" derived from a project name like "MyApp") is converted to `PascalCase` (e.g., "MyApp") for this default. **BREAKING CHANGE**: This change primarily affects Android builds and may alter the final `applicationId` for users who had a custom `bundle.identifier` set, particularly if its casing or final segment differed from the PascalCased `executable_name`. - **Previous Behavior**: The Android `applicationId` was derived from `full_mobile_app_name`. This function effectively used the first two segments of the `bundle_identifier` (user-configured or default) but then **always** appended a PascalCased version of the `executable_name` as the final segment. Example Scenario: - User project name: "MyApp" (CLI set `executable_name` to "my-app") - User configured `bundle.identifier` in Dioxus config: `"com.custom.myapp"` - `bundled_app_name()` (from "my-app"): "MyApp" - Old `applicationId` (via `full_mobile_app_name`): `"com.custom.MyApp"` - **New Behavior**: With the same user configuration (`"com.custom.myapp"`), the `applicationId` will now be: `"com.custom.myapp"` This change ensures the `applicationId` respects the user's explicit `bundle.identifier` string. However, if users were unknowingly relying on the previous behavior where the last segment was always a PascalCased version of the `executable_name` (potentially mismatching their intended lowercase final segment in `bundle.identifier`), their `applicationId` will change upon updating the CLI. According to Google's Android documentation, the `applicationId` must not be changed after an app is published, as this causes Google Play to treat it as a new application. Users who have published Android apps and utilized custom `bundle.identifier` configurations should meticulously verify their `applicationId` after this update to ensure it remains consistent with their published versions or to take appropriate action if a change is detected. * fix: relax validation for bundle.identifier 1. Contains at least one dot ('.'). 2. Does not start with a dot. 3. Does not end with a dot. 4. Does not contain consecutive dots ('..'). e.g., `com.example` or `com.example.app` --------- Co-authored-by: Jonathan Kelley --- packages/cli/src/build/builder.rs | 4 ++-- packages/cli/src/build/request.rs | 34 ++++++++++++++----------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 25e4f5d52..adc0d589f 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -1180,7 +1180,7 @@ We checked the folder: {} ) -> Result<()> { let apk_path = self.build.debug_apk_path(); let session_cache = self.build.session_cache_dir(); - let full_mobile_app_name = self.build.full_mobile_app_name(); + let application_id = self.build.bundle_identifier(); let adb = self.build.workspace.android_tools()?.adb.clone(); // Start backgrounded since .open() is called while in the arm of the top-level match @@ -1261,7 +1261,7 @@ We checked the folder: {} // eventually, use the user's MainActivity, not our MainActivity // adb shell am start -n dev.dioxus.main/dev.dioxus.main.MainActivity - let activity_name = format!("{}/dev.dioxus.main.MainActivity", full_mobile_app_name,); + let activity_name = format!("{application_id}/dev.dioxus.main.MainActivity"); let res = Command::new(&adb) .arg("shell") .arg("am") diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 7b4e1b0f2..e15ca20e3 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -2567,7 +2567,7 @@ impl BuildRequest { android_bundle: Option, } let hbs_data = AndroidHandlebarsObjects { - application_id: self.full_mobile_app_name(), + application_id: self.bundle_identifier(), app_name: self.bundled_app_name(), android_bundle: self.config.bundle.android.clone(), }; @@ -2979,30 +2979,26 @@ impl BuildRequest { kept_features } - pub(crate) fn mobile_org(&self) -> String { - let identifier = self.bundle_identifier(); - let mut split = identifier.splitn(3, '.'); - let sub = split - .next() - .expect("Identifier to have at least 3 periods like `com.example.app`"); - let tld = split - .next() - .expect("Identifier to have at least 3 periods like `com.example.app`"); - format!("{}.{}", sub, tld) - } - pub(crate) fn bundled_app_name(&self) -> String { use convert_case::{Case, Casing}; self.executable_name().to_case(Case::Pascal) } - pub(crate) fn full_mobile_app_name(&self) -> String { - format!("{}.{}", self.mobile_org(), self.bundled_app_name()) - } - pub(crate) fn bundle_identifier(&self) -> String { - if let Some(identifier) = self.config.bundle.identifier.clone() { - return identifier.clone(); + if let Some(identifier) = &self.config.bundle.identifier { + if identifier.contains('.') + && !identifier.starts_with('.') + && !identifier.ends_with('.') + && !identifier.contains("..") + { + return identifier.clone(); + } else { + // The original `mobile_org` function used `expect` directly. + // Maybe it's acceptable for the CLI to panic directly when this error occurs. + // And if we change it to a Result type, the `client_connected` function in serve/runner.rs does not return a Result and cannot call `?`, + // We also need to handle the error in place, otherwise it will expand the scope of modifications further. + panic!("Invalid bundle identifier: {identifier:?}. E.g. `com.example`, `com.example.app`"); + } } format!("com.example.{}", self.bundled_app_name())