Repository: slint-ui/slint (language: rust)

Issue:
Android Build with JDK21 fails with `java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is null` 
<!-- Thanks for opening an issue! 🤗 -->
<!-- IMPORTANT: This issue tracker is for tracking bugs and features request. For questions or help on how to use Slint, please go to the "Discussions" tab -->
<!-- Please mention your platform and the programming language you are using Slint with -->
<!-- For bugs, please give steps on how to reproduce. What is the expected behavior and what do you see instead. -->
<!-- If possible, please include relevant code snippets, in code blocks (```  ```)-->
Tried it on Windows and Linux, with x86_64- and aarch64-linux-android, slint 1.5.0, 1.5.1 and master. Generating the example apk from cargo-apk works fine.

The generated error is in the detail tag below.

# environment
```
> javac --version
javac 21.0.2
```
SDK: Tried 30 and 34 via
```
[package.metadata.android.sdk]
min_sdk_version = 16
target_sdk_version = 30
```
NDK: 26.2.11394342

# reproduction
`cargo apk run -p todo --target x86_64-linux-android --lib`

I'd love for this feature to work. Is there any more information I can provide to help you reproduce this?

<details>
<pre>
error: failed to run custom build command for `i-slint-backend-android-activity v1.6.0 (Z:\slint\internal\backends\android-activity)`

Caused by:
  process didn't exit successfully: `Z:\slint\target\debug\build\i-slint-backend-android-activity-932c2c8243f3d28a\build-script-build` (exit code: 101)
  --- stdout
  cargo:rerun-if-env-changed=ANDROID_HOME
  cargo:rerun-if-env-changed=JAVA_HOME

  --- stderr
  thread 'main' panicked at internal\backends\android-activity\build.rs:111:9:
  Dex conversion failed: Error in Z:\slint\target\x86_64-linux-android\debug\build\i-slint-backend-android-activity-c5d1017c942e9e32\out\java\dev\slint\android-activity\SlintAndroidJavaHelper$1.class:
  java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is null
  Compilation failed with an internal error.
  Exception in thread "main" java.lang.RuntimeException: com.android.tools.r8.CompilationFailedException: Compilation failed to complete, origin: Z:\slint\target\x86_64-linux-android\debug\build\i-slint-backend-android-activity-c5d1017c942e9e32\out\java\dev\slint\android-activity\SlintAndroidJavaHelper$1.class     
        at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:126)
        at com.android.tools.r8.D8.main(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:5)
  Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete, origin: Z:\slint\target\x86_64-linux-android\debug\build\i-slint-backend-android-activity-c5d1017c942e9e32\out\java\dev\slint\android-activity\SlintAndroidJavaHelper$1.class
        at Version.fakeStackEntry(Version_8.2.2-dev.java:0)
        at com.android.tools.r8.M.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:5)
        at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:81)
        at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:32)
        at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:31)
        at com.android.tools.r8.utils.R0.b(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:2)
        at com.android.tools.r8.D8.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:26)
        at com.android.tools.r8.D8.b(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:13)
        at com.android.tools.r8.D8.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:24)
        at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:113)
        ... 1 more
  Caused by: java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is null
        at com.android.tools.r8.graph.u2.<init>(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:5)
        at com.android.tools.r8.graph.p1.c(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:3)
        at com.android.tools.r8.graph.Q3.b(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:10)
        at com.android.tools.r8.internal.Ga.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:873)
        at com.android.tools.r8.graph.T3.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:44)
        at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:129)
        at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:130)
        at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:128)
        at com.android.tools.r8.graph.T3.b(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:2)
        at com.android.tools.r8.dex.a.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:27)
        at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1456)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
        at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)
        Suppressed: java.lang.RuntimeException: java.util.concurrent.ExecutionException: com.android.tools.r8.utils.T0: java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is null
                at com.android.tools.r8.dex.c.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:60)
                at com.android.tools.r8.dex.c.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:9)
                at com.android.tools.r8.dex.c.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:8)
                at com.android.tools.r8.D8.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:29)
                at com.android.tools.r8.D8.d(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:17)
                at com.android.tools.r8.D8.c(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:1)
                at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:28)
                at com.android.tools.r8.utils.R0.b(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:2)
                at com.android.tools.r8.D8.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:26)
                at com.android.tools.r8.D8.b(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:13)
                at com.android.tools.r8.D8.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:24)
                at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:113)
                at com.android.tools.r8.D8.main(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:5)
        Caused by: java.util.concurrent.ExecutionException: com.android.tools.r8.utils.T0: java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is null
                at java.base/java.util.concurrent.ForkJoinTask.reportExecutionException(ForkJoinTask.java:581)
                at java.base/java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:993)
                at com.android.tools.r8.utils.d3.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:64)
                at com.android.tools.r8.dex.c.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:41)
                ... 12 more
        Caused by: com.android.tools.r8.utils.T0: java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is null
                at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:149)
                at com.android.tools.r8.utils.R0.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:128)
                at com.android.tools.r8.graph.T3.b(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:2)
                at com.android.tools.r8.dex.a.a(R8_8.2.2-dev_53a55043254cc5be8ef500331bba25d1b4ca4bc2cd66c555d4358bf672a1f10a:27)
                at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1456)
                at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
                at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
                at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
                at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
                at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)
        Caused by: [CIRCULAR REFERENCE: java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is null]

  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...
Error: Command `cargo build --package todo --lib --target x86_64-linux-android` had a non-zero exit code.

</pre>
</details>


Discussion / hints:
Using jdk 17 makes it work with the slint master branch. From the repository I get a different error about the copyright sign not being ASCII.
```
error: failed to run custom build command for `i-slint-backend-android-activity v1.5.1`

Caused by:
  process didn't exit successfully: `/home/behemoth/source/timetracker/target/debug/build/i-slint-backend-android-activity-2b756c32d0ff7e66/build-script-build` (exit status: 101)
  --- stdout
  cargo:rerun-if-env-changed=ANDROID_HOME
  cargo:rerun-if-env-changed=JAVA_HOME

  --- stderr
  thread 'main' panicked at /home/behemoth/.cargo/registry/src/index.crates.io-6f17d22bba15001f/i-slint-backend-android-activity-1.5.1/build.rs:59:9:
  Java compilation failed: java/SlintAndroidJavaHelper.java:1: error: unmappable character (0xC2) for encoding US-ASCII
  // Copyright ?? SixtyFPS GmbH <info@slint.dev>
               ^
  java/SlintAndroidJavaHelper.java:1: error: unmappable character (0xA9) for encoding US-ASCII
  // Copyright ?? SixtyFPS GmbH <info@slint.dev>
```
Thanks for reporting an issue.

The ASCII thing was fixed in https://github.com/slint-ui/slint/commit/2b7834e7c9ba41cf9ddac8b26ab08d631dfdcaa2

Regarding the first issue, it looks like the d8 script, which is part of the android sdk fails for some reason. But i'm not really understanding why.
You're saying that downgrading Java SDK helps?  Maybe there is an incompatibility with D8 and the new JDK? Or perhpas Slint's build script is not invoking the d8 script properly?


(Was also reported in #5029 with similar error:

```
  Dex conversion failed: Error in /home/bronson/Projects/bike-aid/bike-aid-app/target/aarch64-linux-android/debug/build/i-slint-backend-android-activity-5584ba82a1156d20/out/java/dev/slint/android-activity/SlintInputView$1.class:
  java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is null
  Compilation failed with an internal error.
  Exception in thread "main" java.lang.RuntimeException: com.android.tools.r8.CompilationFailedException: Compilation failed to complete, origin: /home/bronson/Projects/bike-aid/bike-aid-app/target/aarch64-linux-android/debug/build/i-slint-backend-android-activity-5584ba82a1156d20/out/java/dev/slint/android-activity/SlintInputView$1.class
  ...
```

Looks like there is some incompatibility with the android SDK and the java JDK 21.
Googling this error show other result, but none of these link have any solutions:
 - https://stackoverflow.com/questions/77587376/cannot-invoke-string-length-because-parameter1-is-null
 - https://forum.qt.io/topic/151244/online-installed-android-qt-6-6-but-build-requires-newer-version/11
 - https://github.com/HtmlUnit/htmlunit-android/issues/37

I'm not sure what we should do here.
Maybe we can find out what version of the android SDK and JDK are incompatibles and detect that in the build script. Or perhaps there is a better way to invoke D8 that doesn't have this error.

For now, the issue is being "worked around" by printing a message in the debug telling to downgrade Java.

This build script works with JDK 21: <https://docs.rs/crate/jni-min-helper/latest/source/build.rs>.
I was wrong, the problem is probably not about the build script (how D8 is invoked). Adding this non-static method in any Java class used in `jni-min-helper` results in the same error:

```
    public /*static*/ void doSomething() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("!");
            }
        };
    }
```

This produces a anonymous class which is compiled into a `SomeClass$1.class` file (or `$2`, `$3`... if multiple anonymous classes are used). The class file generated by JDK 21 causes this D8 error, while the class file built by JDK 17 works with D8 (tested with build-tools 33.0.2, platform android-30). They are definitely different, despite the `--release 8` parameter passed to `javac` in each of them. When building with JDK 21, replacing the class file of the anonymous class in `OUT_DIR` with the file from JDK 17 makes D8 working.

Comparing them by `javap`, I can't find any difference except one thing: in the class file from JDK 21, the constructor of the anonymous class has a `final mandated` flag for the only item `<no name>` in `MethodParameters`; in the file from JDK 17, `MethodParameters` doesn't exist.

I guess that Java 21 isn't well supported by Android at the moment (not sure): <https://developer.android.com/build/jdks>.
I've tested Android build tools 34.0.0 and 35.0.0 with platform 30 under JDK 21, the D8 from version 35.0.0 built the dex file successfully. With build tools 34.0.0, the result is the same as build tools 33.0.2.

This issue and #6695 may be closed.

Fix patch:
diff --git a/internal/backends/android-activity/build.rs b/internal/backends/android-activity/build.rs
index 46fc7ec799b..52ad17d8c4e 100644
--- a/internal/backends/android-activity/build.rs
+++ b/internal/backends/android-activity/build.rs
@@ -6,6 +6,9 @@ use std::process::Command;
 use std::{env, fs};
 
 fn main() {
+    // unit test: `#[test]` fn are not run in build.rs, so we need to call it manually
+    test_parse_version();
+
     if !env::var("TARGET").unwrap().contains("android") {
         return;
     }
@@ -55,12 +58,7 @@ fn main() {
         // old version of java used stderr
         version_output = String::from_utf8_lossy(&o.stderr);
     }
-    let version = version_output.split_whitespace().nth(1).unwrap_or_default();
-    let mut java_ver: i32 = version.split('.').next().unwrap_or("0").parse().unwrap_or(0);
-    if java_ver == 1 {
-        // Before java 9, the version was something like javac 1.8
-        java_ver = version.split('.').nth(1).unwrap_or("0").parse().unwrap_or(0);
-    }
+    let java_ver = parse_javac_version_output(&version_output);
     if java_ver < 8 {
         panic!("The detected Java version is too old. The minimum required version is Java 8. Your Java version: {version_output:?} (parsed as {java_ver})")
     }
@@ -116,10 +114,11 @@ fn main() {
 
     if !o.status.success() {
         eprintln!("Dex conversion failed: {}", String::from_utf8_lossy(&o.stderr));
+
         if java_ver >= 21 {
-            eprintln!("WARNING: JDK version 21 is known to cause an error.");
+            eprintln!("WARNING: JDK version 21 is known to cause an error with older android SDK");
             eprintln!("See https://github.com/slint-ui/slint/issues/4973");
-            eprintln!("Try downgrading your version of Java to something like JDK 17.");
+            eprintln!("Try downgrading your version of Java to something like JDK 17, or upgrade to the SDK build tools 35");
         }
         panic!("Dex conversion failed");
     }
@@ -127,6 +126,30 @@ fn main() {
     println!("cargo:rerun-if-changed=java/{java_class}");
 }
 
+fn parse_javac_version_output(version_output: &str) -> i32 {
+    let version = version_output
+        .split_whitespace()
+        .nth(1)
+        .and_then(|v| v.split('-').next())
+        .unwrap_or_default();
+    let mut java_ver: i32 = version.split('.').next().unwrap_or("0").parse().unwrap_or(0);
+    if java_ver == 1 {
+        // Before java 9, the version was something like javac 1.8
+        java_ver = version.split('.').nth(1).unwrap_or("0").parse().unwrap_or(0);
+    }
+    java_ver
+}
+
+//#[test] doesn't work in build.rs so it is called from main. Impact on build time should be negligible
+fn test_parse_version() {
+    assert_eq!(parse_javac_version_output("javac 1.8.0_292"), 8);
+    assert_eq!(parse_javac_version_output("javac 17.0.13"), 17);
+    assert_eq!(parse_javac_version_output("javac 21.0.5"), 21);
+    assert_eq!(parse_javac_version_output("javac 24-ea"), 24);
+    assert_eq!(parse_javac_version_output("error"), 0);
+    assert_eq!(parse_javac_version_output("javac error"), 0);
+}
+
 fn env_var(var: &str) -> Result<String, env::VarError> {
     println!("cargo:rerun-if-env-changed={}", var);
     env::var(var)


Test patch: