Android development: rust vs java/kotlin

For those familiar in both (1) Rust and (2) at least one of Java/Kotlin -- what do you use for android development?

If Rust, I'm also interested in hearing the biggest obstacles you have run into (and whether you can directly use the Android 'native' api instead of the JVM api).

Hi there!
I'm currently working on a personal project which uses android/rust.

I don't know how it would have worked in Java, but there are a few things I'd like to note:

  • It's a very barebones interface that is exposed by the NDK, meaning you need to do everything from loading OpenGL ES to writing (or using a preexisting) gui/drawing library. I currently use a locally modified piston clone.
  • Optimizations are important, without them the rust code unfortunately doesn't run very fast. With them, code runs smoothly.
  • You don't get stack traces on panic; they're either obscured by release mode, or unattainable because of a bug in android, so either println debugging or an actual debugger are necessary.
  • Because, as mentioned earlier, the NDK provides essentially event handling, the rest, more complicated things you'd want to do are left up to you using the JNI. Which can unfortunately lead to some ugly code.
  • The biggest obstacle in developing on Android I have faced is not knowing what the obstacle was. I was stuck trying to debug an glutin for a week only to find it was a debug flag that was wrecking everything. And since egl isn't a very nice interface to work with, I was left with println debugging.

I'm not quite sure what you mean by the android 'native' API, since there's the NDK (native development kit), and the JNI (java native interface).

A noteworthy point to make though is that given the progress made in cargo-apk, I can have a java-less codebase, so I don't have to juggle languages.

If you've any more questions please let me know; I'll try to answer them based on my experience.

5 Likes

Most of the time, you should just use Kotlin. Recreating all the stuff you need to make a good app any other way is not worth it.

5 Likes
  1. Thank you for your detailed reply.

  2. In my current Rust/wasm32 app, I create a full sized canvas, open a WebGL context and draw everything in WebGL. Text is rendered via textures. "Buttons" consists of GL Lines, Quads, and Textures. Hover effects is by manually toggling button colors. Drag/Drag involves manually updating locations of quads.

You describe:

We're basically operating on the same level right? :slight_smile:

This sounds important. I have no idea what this means. Is it just a matter of ```--release`` or is something else going on?

In wasm32, I use set panic hook to capture panics. Then I can print out a file name / line number before it dies. On Android/NDK, on crashes, even if you don't get full stack trace, can you at least get a file name / line number before program dies?

I don't want to sound entitled here -- Is there something I can git clone? This sounds like the type of issue where copy/paste is much easier than spending a weekend trying to figure out all the cargo / compiler / linkier flags.

A minimal example that executed gl clear + draws a single quad on Android/NDK would be huge.

Be careful, since lines/points aren't stable throughout OpenGLES implementations, and I found that the one I was personally using (On a Pixel 2 for reference) had weird primitive count bugs, so I ended up having to use a quad.

...yeah, technically, but there's a lot more niche things you need to worry about when writing on NDK/GL. Like, as I mentioned, a single debug flag had me stumped for a week

Just --release works fine, but I personally use

[profile.release]
lto = true

Here's some sample output with the usual logcat stuff cleaned out:

RustAndroidGlueStdouterr: thread '<unnamed>' panicked at 'explicit panic', src\drawable\water.rs:59:29
RustAndroidGlueStdouterr: stack backtrace:
RustAndroidGlueStdouterr:    0: <unknown>
RustAndroidGlueStdouterr:    1: <unknown>
RustAndroidGlueStdouterr:    2: <unknown>
RustAndroidGlueStdouterr:    3: <unknown>
RustAndroidGlueStdouterr:    4: <unknown>
RustAndroidGlueStdouterr:    5: <unknown>
RustAndroidGlueStdouterr:    6: <unknown>
RustAndroidGlueStdouterr:    7: <unknown>
RustAndroidGlueStdouterr: fatal runtime error: failed to initiate panic, error 9
libc    : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 2773 (rust.Trees), pid 2747 (rust.Trees)

This is when I just inserted a random panic!() statement. If I purposely make an error in my shader code it gets more complex:

thread '<unnamed>' panicked at 'Error in the Fragment shader: ERROR: 0:19: 'P' : Syntax error:  syntax error
ERROR: 1 compilation errors.  No code generated.
', src\shader\mod.rs:16:19
stack backtrace:
   0: <unknown>
   1: <unknown>
   2: <unknown>
   3: <unknown>
   4: <unknown>
   5: <unknown>
   6: <unknown>
   7: <unknown>
fatal runtime error: failed to initiate panic, error 9

I've set it up to try and extract the error, but in some cases the GLSL ES compiler will hop in and say something prior to that.

Let me see if I can create essentially the project shown in the thread I mentioned above. I'll also include my local opengl_graphics clone which has been modified to allow more flexible shader interop and to make it compatible with newer OpenGL ES versions in the build script and some other places since the opengles_graphics crate is heavily out of date and my own optimisticpeach_opengles_graphics is already old and I was very inexperienced with OpenGL at the time.

1 Like

I'm a big fan of Kotlin/IntelliJ (so much so it pulled me away from Emacs). However, for graphics / real time work, I really want my RAII, memory layout, and ability to control when allocations happen.

1 Like
  1. GL_Lines:

You're right, I over simplified. I'm actually using Quads + shaders to ensure the "line" has correct width in screen space.

  1. Debug Flag

You're dealing with far nastier issues. The equiv would be "is Chrome correctly implementing WebGL spec?" -- and I have not run into those issues.

  1. lack of stack traces

Thanks the concrete "stack traces". They look good enough. For rust code, all I need is file name + line number; and for vertex/frag shaders, all I need is just filename/line_number of compile error (during runtime of rust code).

=====

  1. git clone minimal example

Thanks! Much appreciated.

  1. This is to sanity check when I start replicating step 4. How long is your "edit, compile, upload to phone/tablet, run" cycle?

I'm guessing that compile time doesn't change much.

As for "upload to phone/tablet" is this as fast as "send 10-20MB file over USB3", or are we looking at some thing horrendous ?

OpenGL drives suck sometimes. There's no other way to put it.

I'm running on windows/IntelliJ, so just keep that in mind when trying to compile. Also, a few notes on this:

  • I am including two helper libraries:

    • matrices which is quite central to my project but is not specific to rendering or the like. I included it because it is kind of integrated with my android base crate.
    • android_rs_base is a modified version of my own android_rs_base which implements some edge cases and setup for you. It also currently manages shaders (Albeit in a not so rusty way since it uses boxed trait objects).

    You don't need to include matrices; the majority of its usage is in ./src/storage.rs of android_rs_base.

  • Remember to follow some guide for setting up the NDK if you haven't already. I made one about a year ago here if you need one, although its accuracy is questionable.

It's not that bad; I just timed the app I pushed and it took about 23s, but that can be optimized if you don't make opengl_graphics rebuild the opengl api every time by changing line 10 of build.rs to this:

let mut file = File::create(&Path::new(&dest).join("../gl.rs")).unwrap();

and the gl mod declaration in lib.rs to this

#[allow(non_upper_case_globals, missing_docs, bare_trait_objects, unused_mut)]
#[path = "../../gl.rs"]
pub mod gl;

I chose ../gl.rs because the output directory I'm supposed to output to (In target) is inaccessible by IntelliJ, and putting it in ./src causes it to rebuild each time. Otherwise, there's little to be done; cargo apk rebuilds the glue every time unfortunately, and building an APK is just slow.

Anyway, without further ado, here are the links:

Compile using cargo apk --release.

If you have any questions then reach me on this thread, via a forum message or an issue on the repos (Slowest though, since I check github less often than I do this forum).

2 Likes
  1. Thank you for your hard work.

  2. I've cloned all three repos, and reading through the READMEs -- it's clear you had to battle numerous bizarre errors.

  3. Thanks again for sharing!

2 Likes

And if that's all you're doing, it completely screws over the blind. Does the web platform even have a way for you to expose buttons within <canvas> to screenreaders?

2 Likes

Checkout Flutter, "native" for Android/iOS/PWAs and FFI for heavy duty.

Full time android developer and hobbyist rust developer here. For android development I am fully Android Studio and Kotlin. I haven't done much work trying to bridge the two. My current understanding is you can access parts of the android framework though the NDK but it will be limited what you can do and the interfaces aren't terribly fun to work with when you are used to the high level abstractions. I don't have a lot of experience with it beyond just looking into it but I'm always happy to answer android / kotlin /java questions if you have any.

1 Like