Tauri Alpha Building for Android

If you've followed the earlier posts in the series it's pretty easy to see that I'm leaning into this Tarui/Svelte/Rust stack but there are still some concerns that would make me turn around and run away(more likely run to the Tauri issue queue). First among those are build and integration complexity, it's not fully documented yet, so let's walk through that process and see if I can figure it out. I'll be working with the codebase from the previous blog post on Phaser.js integration so first I'll tag that repo with a v0.1 release and then create a development branch for building.

phasor-integration-demo on  main via  v21.1.0 on ☁️  (us-east-1)
❯ git tag -a v0.1 -m "Phaser integration: https://gatewaynode.com/tauri-2-alpha-and-phaser-3"
phasor-integration-demo on  main via  v21.1.0 on ☁️  (us-east-1)
❯ git tag -n
v0.1            Phaser integration: https://gatewaynode.com/tauri-2-alpha-and-phaser-3
phasor-integration-demo on  main via  v21.1.0 on ☁️  (us-east-1)
❯ git push origin v0.1
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 214 bytes | 214.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:gatewaynode/tauri_phaserjs_walkthrough.git
 * [new tag]         v0.1 -> v0.1
❯ git checkout -b android_build_dev
Switched to a new branch 'android_build_dev'

Make sure you have the prerequisites installed according to the Tauri beta website and run some simple smoke tests to make sure everything is setup as needed, like:

phasor-integration-demo on  android_build_dev via  v21.1.0 on ☁️  (us-east-1)
❯ echo $JAVA_HOME
/Applications/Android Studio.app/Contents/jbr/Contents/Home

phasor-integration-demo on  android_build_dev via  v21.1.0 on ☁️  (us-east-1)
❯ echo $ANDROID_HOME
/Users/.../Android/Sdk

phasor-integration-demo on  android_build_dev via  v21.1.0 on ☁️  (us-east-1)
❯ echo $NDK_HOME
/Users/.../Android/Sdk/ndk/25.0.8775105

phasor-integration-demo on  android_build_dev via  v21.1.0 on ☁️  (us-east-1)
❯ rustup target list
...
aarch64-linux-android (installed)
...
armv7-linux-androideabi (installed)
...
i686-linux-android (installed)
...
x86_64-linux-android (installed)
...

And here we hit the end of the documentation, this is pre-release so absolutely expected, let's just see how far I can get. The build page contains a small stub command for Android pnpm tauri [ios|android] build that we can work with. I do not have pnpm installed so let's see if that will get us to next steps.

phasor-integration-demo on  android_build_dev [?] via  v21.1.0 on ☁️  (us-east-1)
❯ npm install -g pnpm

added 1 package in 457ms

1 package is looking for funding
  run `npm fund` for details

phasor-integration-demo on  android_build_dev [?] via  v21.1.0 on ☁️  (us-east-1)
❯ pnpm tauri android build

> phasor-integration-demo@0.0.0 tauri /Users/.../code/phasor-integration-demo
> tauri "android" "build"

       Error Android Studio project directory /Users/.../code/phasor-integration-demo/src-tauri/gen/android doesn't exist. Please run `tauri android init` and try again.
 ELIFECYCLE  Command failed with exit code 1.

An error, but a good error, it tells us what to do to fix it.

phasor-integration-demo on  android_build_dev [?] via  v21.1.0 on ☁️  (us-east-1)
❯ pnpm tauri android init

> phasor-integration-demo@0.0.0 tauri /Users/anon/code/phasor-integration-demo
> tauri "android" "init"

        Info "/Users/anon/code/phasor-integration-demo/node_modules/.bin/tauri" relative to "/Users/anon/code/phasor-integration-demo/src-tauri" is "../node_modules/.bin/tauri"
action request:  to initialize Android environment; Android support won't be usable until you fix the issue below and re-run `tauri android init`!
    Have you installed the Android SDK? The `ANDROID_HOME` environment variable is set, but doesn't point to an existing directory.
victory: Project generated successfully!
    Make cool apps! 🌻 🐕 🎉

Ooo, a warning that our environment is not setup correctly, but also a success that's probably not really a success. Now my context is not one of mobile development, so when I originally setup these dependencies months ago it was likely in error. Tracking the error message to StackOverflow there is a recommendation which eventually shows where I made a mistake, apparently I set my ANDROID environment variables on the wrong path. This took a little click ops in Android Studio that you can see by opening the app (on MacOS) and navigating to Preferences --> Languages & Frameworks --> Android SDK and installing or updating the SDK.

Updating the .zshrc file got me to a new error with pnpm tauri android init, NDK needed to be added from Android Studio. This is on the same window in Android Studio as the SDK component installer in a tab called SDK Tools.

So we install that and then tried the init command again:

phasor-integration-demo on  android_build_dev [?] via  v21.1.0 on ☁️  (us-east-1)
❯ pnpm tauri android init

> phasor-integration-demo@0.0.0 tauri /Users/anon/code/phasor-integration-demo
> tauri "android" "init"

        Info "/Users/anon/code/phasor-integration-demo/node_modules/.bin/tauri" relative to "/Users/anon/code/phasor-integration-demo/src-tauri" is "../node_modules/.bin/tauri"
Generating Android Studio project...
        Info "/Users/anon/code/phasor-integration-demo/src-tauri" relative to "/Users/anon/code/phasor-integration-demo/src-tauri/gen/android/phasor_integration_demo" is "../../../"
victory: Project generated successfully!
    Make cool apps! 🌻 🐕 🎉

No warnings and it looks like we are good to go to try the build command now. So pnpm tauri android build and we are compiling away, and then an error:

phasor-integration-demo on  android_build_dev [!?] via  v21.1.0 on ☁️  (us-east-1)
❯ pnpm tauri android build

> phasor-integration-demo@0.0.0 tauri /Users/anon/code/phasor-integration-demo
> tauri "android" "build"

        Info detected host target triple "aarch64-apple-darwin"
  Downloaded tao-macros v0.1.2
  ... # A whole lot of downloading and compiling
   Compiling reqwest v0.11.23
   Compiling tauri-plugin-shell v2.0.0-alpha.6
    Finished release [optimized] target(s) in 47.05s
        Info symlinking lib "/Users/.../code/phasor-integration-demo/src-tauri/target/aarch64-linux-android/release/libphasor_integration_demo_lib.so" in jniLibs dir "/Users/.../code/phasor-integration-demo/src-tauri/gen/android/app/src/main/jniLibs/arm64-v8a"
        Info "/Users/.../code/phasor-integration-demo/src-tauri/target/aarch64-linux-android/release/libphasor_integration_demo_lib.so" requires shared lib "libandroid.so"
        Info "/Users/.../code/phasor-integration-demo/src-tauri/target/aarch64-linux-android/release/libphasor_integration_demo_lib.so" requires shared lib "libdl.so"
        Info "/Users/.../code/phasor-integration-demo/src-tauri/target/aarch64-linux-android/release/libphasor_integration_demo_lib.so" requires shared lib "liblog.so"
        Info "/Users/.../code/phasor-integration-demo/src-tauri/target/aarch64-linux-android/release/libphasor_integration_demo_lib.so" requires shared lib "libm.so"
        Info "/Users/.../code/phasor-integration-demo/src-tauri/target/aarch64-linux-android/release/libphasor_integration_demo_lib.so" requires shared lib "libc.so"
       Error You must change the bundle identifier in `tauri.conf.json > tauri > bundle > identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.
 ELIFECYCLE  Command failed with exit code 1.

Close, but the default bundle name needs to be set. This might be worth an issue in the Tauri project so that the project setup script handles it, so I'll check there and file a bug if it looks like it might be helpful. But let's change that to "phaser.integration.demo" and see what we get.

phasor-integration-demo on  android_build_dev [!?] via  v21.1.0 on ☁️  (us-east-1)
❯ pnpm tauri android build

> phasor-integration-demo@0.0.0 tauri /Users/.../code/phasor-integration-demo
> tauri "android" "build"

       Error Project directory /Users/.../code/phasor-integration-demo/src-tauri/gen/android/app/src/main/java/phaser/integration/phasor_integration_demo does not exist. Did you update the package name in `Cargo.toml` or the bundle identifier in `tauri.conf.json > tauri > bundle > identifier`? Save your changes, delete the `gen/android` folder and run `tauri android init` to recreate the Android project.
 ELIFECYCLE  Command failed with exit code 1.

Ah, so I needed to clean up the cruft from the first attempted build. This error is really good but doesn't go quite far enough, after updating the bundle identifier we need to delete the ./src-tauri/gen/android folder, go back and re-run the init command, and then run the build command.

phasor-integration-demo on  android_build_dev [!?] via  v21.1.0 on ☁️  (us-east-1)
❯ pnpm tauri android build

> phasor-integration-demo@0.0.0 tauri /Users/.../code/phasor-integration-demo
> tauri "android" "build"

        Info detected host target triple "aarch64-apple-darwin"
   Compiling wry v0.35.2
... # A whole lot of compilation output
See https://docs.gradle.org/8.0/userguide/command_line_interface.html#sec:command_line_warnings
    Finished 1 APK at:
        /Users/.../code/phasor-integration-demo/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release-unsigned.apk

    Finished 1 AAB at:
        /Users/.../code/phasor-integration-demo/src-tauri/gen/android/app/build/outputs/bundle/universalRelease/app-universal-release.aab

And we have an APK file. Not too bad.

In this journey I went to to use ADB over USB interface to install the APK file on an Android phone I have. I noticed by the filename this was going to fail right away as the APK is unsigned, this is what that looks like.

phasor-integration-demo on  android_build_dev [!?] via  v21.1.0 on ☁️  (us-east-1)
❯ adb install ~/Desktop/app-universal-release-unsigned.apk
Performing Streamed Install
adb: failed to install /Users/.../Desktop/app-universal-release-unsigned.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl727785266.tmp/base.apk: Attempt to get length of null array]

So a bit off the beaten path, but here is some documentation in the Tauri issues on getting code signing working:

https://github.com/tauri-apps/tauri-docs/issues/1674

Two articles are linked in there, and I'm going to try this one as it was recommended as the same pattern Tauri is using https://next--tauri.netlify.app/next/guides/distribution/sign-android/. This involves creating a key.properties file and adding code signing steps to the Gradle build. I've provided an EXAMPLE.key.properties file in the code repo to help. It is very important that you do not commit your keystore or the key.properties file to the code repo and in non-local build pipelines and these files must be stored in some sort of secrets management system outside any code base in your CI/CD workflow.

So follow along and add the key file and modify the gradle build, run the build again and:

... // Lot's of build output
See https://docs.gradle.org/8.0/userguide/command_line_interface.html#sec:command_line_warnings
    Finished 1 APK at:
        /Users/anon/code/phasor-integration-demo/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk

    Finished 1 AAB at:
        /Users/anon/code/phasor-integration-demo/src-tauri/gen/android/app/build/outputs/bundle/universalRelease/app-universal-release.aab

They are signed. So let's try the streaming install:

phasor-integration-demo on  android_build_dev [!+] via  v21.1.0 on ☁️  (us-east-1)
❯ adb install /Users/anon/code/phasor-integration-demo/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk
Performing Streamed Install
Success

Well it mostly worked, the images are broken, the screen size is off, but it installs and renders:

More yet to do to call the mobile build complete, but this is semi-working for now.

Code: https://github.com/gatewaynode/tauri_phaserjs_walkthrough

And a thank you to Simon on the Tauri discord who quickly found a reference for APK signing workflows that let me figure out that part (and without having to open Android Studio which makes me very happy).

UPDATE: The broken images were the first place I looked, the hard coded http://localhost/:1420 address. I fixed it by dynamically checking the hostname with document.location so it gets set correctly anywhere it runs. In the case of Android this will be http://tauri.localhost/ , the one thing to look out for is this value is not a normal string so in vanilla Javascript I typecast this to a string with this code:

let install_host = String(document.location);
...
    this.load.setBaseURL(install_host);