diff --git a/src/content/docs/develop/Plugins/develop-mobile.mdx b/src/content/docs/develop/Plugins/develop-mobile.mdx index a9c9d1bded..328e2f81f6 100644 --- a/src/content/docs/develop/Plugins/develop-mobile.mdx +++ b/src/content/docs/develop/Plugins/develop-mobile.mdx @@ -355,6 +355,129 @@ Use a nullable type and set the default value on the command function instead. Required arguments are defined as `let : Type` ::: +## Calling Rust From Mobile Plugins + +It is often preferable to write plugin code in Rust, for performance and reusability. While Tauri doesn't directly provide a mechanism to call Rust from your plugin code, using JNI on Android and FFI on iOS allows plugins to call shared code, even when the application WebView is suspended. + +### Android + +In your plugin's `Cargo.toml`, add the jni crate as a dependency: + +```toml +[target.'cfg(target_os = "android")'.dependencies] +jni = "0.21" +``` + +Load the application library statically and define native functions in your Kotlin code: + +```kotlin +private const val TAG = "MyPlugin" + +init { + try { + // Load the native library (libapp_lib.so) + // This is the shared library built by Cargo with crate-type = ["cdylib"] + System.loadLibrary("app_lib") + Log.d(TAG, "Successfully loaded libapp_lib.so") + } catch (e: UnsatisfiedLinkError) { + Log.e(TAG, "Failed to load libapp_lib.so", e) + throw e + } +} + +external fun helloWorld(name: String): String? +``` + +Then in your plugin's Rust code, define the function JNI will look for: + +```rust +#[cfg(target_os = "android")] +#[no_mangle] +pub extern "system" fn Java_com_example_HelloWorld_helloWorld( + mut env: JNIEnv, + _class: JClass, + name: JString, +) -> jstring { + log::debug!("Calling JNI Hello World!"); + let result = format!("Hello, {}!", name); + + match env.new_string(result) { + Ok(jstr) => jstr.into_raw(), + Err(e) => { + log::error!("Failed to create JString: {}", e); + std::ptr::null_mut() + } + } +} +``` + +### iOS + +iOS only uses standard C FFI, so doesn't need any new dependencies. Add the hook in your Swift code, as well as any necessary cleanup: + +```swift +@_silgen_name("hello_world_ffi") +private static func helloWorldFFI(_ name: UnsafePointer) -> UnsafeMutablePointer? + +@_silgen_name("free_hello_result_ffi") +private static func freeHelloResult(_ result: UnsafeMutablePointer) + +static func helloWorld(name: String) -> String? { + // Call Rust FFI + let resultPtr = name.withCString({ helloWorldFFI($0) }) + + // Convert C string to Swift String + let result = String(cString: resultPtr) + + // Free the C string + freeHelloResult(resultPtr) + + return result +} + +``` + +Then, implement the Rust side: + +```rust +#[no_mangle] +pub unsafe extern "C" fn hello_world_ffi(c_name: *const c_char) -> *mut c_char { + let name = match CStr::from_ptr(c_name).to_str() { + Ok(s) => s, + Err(e) => { + log::error!("[iOS FFI] Failed to convert C string: {}", e); + return std::ptr::null_mut(); + } + }; + + let result = format!("Hello, {}!", name); + + match CString::new(result) { + Ok(c_str) => c_str.into_raw(), + Err(e) => { + log::error!("[iOS FFI] Failed to create C string: {}", e); + std::ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn free_hello_result_ffi(result: *mut c_char) { + if !result.is_null() { + drop(CString::from_raw(result)); + } +} +``` + +## Android 16KB Memory Pages + +Google is moving to make 16KB memory pages a requirement in all new Android app submissions. In order to build Tauri apps that meet this requirement, add the following to `.cargo/config.toml`: + +```toml +[target.aarch64-linux-android] +rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"] +``` + ## Permissions If a plugin requires permissions from the end user, Tauri simplifies the process of checking and requesting permissions.