Browse Source

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 <jkelleyrtp@gmail.com>
Fontlos 3 days ago
parent
commit
bfb0642a1d
2 changed files with 17 additions and 21 deletions
  1. 2 2
      packages/cli/src/build/builder.rs
  2. 15 19
      packages/cli/src/build/request.rs

+ 2 - 2
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")

+ 15 - 19
packages/cli/src/build/request.rs

@@ -2567,7 +2567,7 @@ impl BuildRequest {
             android_bundle: Option<crate::AndroidSettings>,
         }
         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())