Java/Kotlin ffi on dioxus Mobile
Hello, What is the strategy to get access to java/kotlin ffi functions/libraries on Android in a dioxus mobile app? I can make a java library and transfer it in the assets folder. I know it's there in the build and i can get a local path to it - smth like app/src/main/assets/filename, but to initiate ffi with JNI, i seem to need a complete path... either that or the file isn't really there at all!
From reading around, there's of course java/kotlin code that could pull out the path to this, or copy the file to somewhere it's accessible from rust, but... because all the app initiation is handled by dioxus cli, i cannot interject and therefore haven't found a way to initiate any ffi.
Is there an approach to this I've not seen (apologies if that's not a pure Dioxus question) - and if not, could it be considered for a future feature - maybe a helper fn, or at least a guide on how this could be done?
many thanks - looking forward to 0.7!
I'm not part of the dioxus project, but...
Dioxus uses tao for cross platform windowing and wry for cross platform Webviews. tao initializes the ndk_context library with the VM and the main activity (WryActivity, subclass of AppCompatActivity)
https://github.com/tauri-apps/tao/blob/67f00588b4927271cb8b89f1d0c10db3c9727093/src/platform_impl/android/ndk_glue.rs#L219
You can get access to the VM via ndk_context::android_context(). From there, get the VM and attach it to the current thread to get a JNI environment. From there, use JNIenv to find_class() and create new_object().
let ctx = ndk_context::android_context();
let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.expect("cannot get vm from ctx");
let mut env = vm.attach_current_thread().expect("cannot attach to current thread");
The only problem is attaching to the current thread. If you call this from the main thread (sometimes called the UI thread) your JNI env will have access to the apk's class loader. If not, you will only get a bare OS class loader that can't load anything from the apk or dependencies. This can be fixed by loading the context and class loader of the activity (also available from ndk_context::android_context()) You have to find the context and class loader of the activity and use that result to load more classes.
something like this
fn load_clazz_with_activity_loader<'a, 'b>(
activity: &'a jni::objects::JObject,
env: &'a mut jni::JNIEnv,
classname: &'a str
) -> Result<JClass<'b>, Error>
{
let loader = env.call_method(activity, "getClassLoader", "()Ljava/lang/ClassLoader;", &[]).unwrap();
let message = env.new_string(classname).unwrap();
let loaded_clazz = env.call_method(loader.l().unwrap(), "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", &[JValue::Object(&message)])?;
Ok(
unsafe { JClass::from_raw(loaded_clazz.l().unwrap().as_raw()) }
)
}
I needed to make the webview a pub property of DesktopService. There's probably a better way to do this.
#[component]
pub fn Hero() -> Element {
rsx! {
div {
id: "hero",
img { src: HEADER_SVG, id: "header" }
div { id: "links",
a { href: "https://dioxuslabs.com/learn/0.6/", "📚 Learn Dioxus" }
a { href: "https://dioxuslabs.com/awesome", "🚀 Awesome Dioxus" }
a { href: "https://github.com/dioxus-community/", "📡 Community Libraries" }
a { href: "https://github.com/DioxusLabs/sdk", "⚙️ Dioxus Development Kit" }
a { href: "https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus", "💫 VSCode Extension" }
a { href: "https://discord.gg/XgGxMSkvUM", "👋 Community Discord" }
button { onclick: move |_| display_toast(), "Hello JNI" }
}
}
}
}
fn display_toast()
{
let _w = window().webview.handle(); // pub viz hack in the source of dioxus-desktop/src/desktop_context.rs
_w.exec(display_toast_jni);
}
#[allow(dead_code)]
fn display_toast_jni(env: &mut JNIEnv, _activity: &JObject, _webview: &JObject)
{
let ctx = ndk_context::android_context();
let toast_clazz = env.find_class("android/widget/Toast").expect("cannot load context class");
let message = env.new_string("HELLO World, from JNI").unwrap();
let ctx_as_jobect = unsafe { JObject::from_raw(ctx.context() as jni::sys::jobject) };
let ctx_as_param = JValueGen::Object(&ctx_as_jobect);
let args = &[ctx_as_param, JValue::Object(&message), JValue::Int(0)];
let toastobj = env.call_static_method(
toast_clazz,
"makeText",
"(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
args
);
if let Err(e) = toastobj.as_ref() {
debug!("err: {}", e);
let _ = env.exception_describe().unwrap();
}
let mut toastobj = toastobj.unwrap().l().unwrap();
let result = env.call_method(
&mut toastobj,
"show",
"()V",
&[]
);
if let Err(e) = result.as_ref() {
debug!("err: {}", e);
env.exception_describe().unwrap();
}
}