3

I'm testing Rust with JNI async execution. I want to do execute requests in Rust and return the result to Android asynchronously with callback. I'm testing code to execute the request in the command line and it works fine.

That is how it works on command line:

Callback struck:

struct Processor {
    pub(crate) callback: Box<dyn FnMut(String)>,
}

impl Processor {

    fn set_callback(&mut self, c: impl FnMut(String) + 'static) {
        self.callback = Box::new(c);
    }

    fn process_events(&mut self, result: String) {
        (self.callback)(result);
    }
}

Tokio/reqwest:

const DATA_URL: &str = "https://pokeapi.co/api/v2/pokemon/1/";

#[tokio::main]
pub async fn load_swapi_async_with_cb(callback: Box<dyn FnMut(String)>) -> Result<(), Box<dyn std::error::Error>> {
    println!("load_swload_swapi_async_with_cbapi_async started");
    let mut cb = Processor {
        callback: Box::new(callback),
    };
    let body = reqwest::get(DATA_URL)
        .await?
        .json::<HashMap<String, String>>()
        .await?;
    //println!("{:#?}", body);
    let name = match body.get("name") {
        Some(name) => name,
        None => "Failed to parse"
    }.to_string();

    println!("Name is: {} ", name);
    cb.process_events(name);
    Ok(())
}

And JNI part:

    #[no_mangle]
    #[allow(non_snake_case)]
    pub extern "C" fn Java_com_omg_app_greetings_MainActivity_callback(env: JNIEnv,
                                                                       _class: JClass,
                                                                       callback: JObject) {

        static callback: dyn FnMut(String) + 'static = |name| {
        let response = env.new_string(&name).expect("Couldn't create java string!");
            env.call_method(callback, "rustCallbackResult", "(Ljava/lang/String;)V",
                        &[JValue::from(JObject::from(response))]).unwrap();
        };

        pokemon_api(callback);
    }

And pokemon API method:

#[no_mangle]
pub extern fn pokemon_api(callback: impl FnMut(String) + 'static) {
    let cb_box = Box::new(callback);
    swapi::load_swapi_async_with_cb(cb_box);
}

The error I'm facing:

  • JNI ENV env non-constant value:
let response = env.new_string(&name).expect("Couldn't create java string!");
   |                        ^^^ non-constant value

  • callback - doesn't have a size known at compile-time:
static callback: dyn FnMut(String) + 'static = |name| {
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time

I was checking how this working, but example seems to be out of date: * https://github.com/mozilla/rust-android-gradle/blob/master/samples/rust/src/lib.rs

ilbets
  • 710
  • 1
  • 9
  • 35
  • https://users.rust-lang.org/t/rust-jni-android-async-callback/41016 – ilbets Apr 15 '20 at 12:25
  • I don't know much about Rust, but will the callback be executed in a different thread? Each `JNIEnv*` is bound to the thread that created it, and it is a fatal error to use `JNIEnv`s on a different thread. Luckily you can [`AttachCurrentThreadAsDaemon`](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread_as_daemon) to get a valid `JNIEnv`. – Botje Apr 16 '20 at 07:04
  • I never thought about it.. let me investigate more on this. Thanks – ilbets Apr 17 '20 at 13:25

1 Answers1

1

I fixed with my problem with the usage of the https://github.com/Dushistov/rust_swig After you integrate it, it will autogenerate code and you can check how it does it.

ilbets
  • 710
  • 1
  • 9
  • 35