콘텐츠로 이동
Tauri

모바일 플러그인 개발

플러그인은 Kotlin(또는 Java)과 Swift로 작성된 네이티브 모바일 코드를 실행할 수 있습니다. 기본 플러그인 템플릿에는 Kotlin을 사용한 “Android 라이브러리 프로젝트”와 Rust 코드에서 어떻게 실행시키는지(트리거하는지)를 설명하는 모바일 커맨드 샘플을 포함한 “Swift 패키지”가 포함되어 있습니다.

새로운 플러그인 프로젝트를 초기화하려면 이전 장 플러그인 개발의 절차를 따르십시오.

기존 플러그인에 Android 또는 iOS 기능을 추가하고 싶다면, plugin android initplugin ios init을 사용하여 모바일 라이브러리 프로젝트를 부트스트랩(시작)하고 필요한 변경 사항을 통합할 수 있습니다.

기본 플러그인 템플릿은 플러그인 구현을 desktop.rsmobile.rs라는 두 개의 별도 모듈로 분할합니다.

“데스크톱 구현”에서는 Rust 코드를 사용하여 기능을 구현하지만, “모바일 구현”에서는 네이티브 모바일 코드로 메시지를 보내 함수를 실행하고 결과를 얻습니다. 두 구현에서 공통 로직이 필요한 경우, lib.rs에서 정의합니다:

src/lib.rs
use tauri::Runtime;
impl<R: Runtime> <plugin-name><R> {
pub fn do_something(&self) {
// 여기에 데스크톱과 모바일 간에 공유되는 구현(이 예에서는 do_something 함수의 내용)을 정의합니다
}
}

이 구현은 커맨드와 Rust 코드 양쪽에서 사용 가능한 API를 공유하는 프로세스를 간소화합니다.

Android용 Tauri 플러그인은 app.tauri.plugin.Plugin을 확장하고 app.tauri.annotation.TauriPlugin으로 어노테이션된 “Kotlin 클래스”로 정의됩니다. app.tauri.annotation.Command로 어노테이션된 각 메서드는 Rust 또는 JavaScript에서 호출할 수 있습니다.

Note

어노테이션 annotation. 데이터에 관련 정보를 주석으로 부여하는 것. 자세한 내용은 Wikipedia를 참조하십시오.

Tauri는 Android 플러그인 구현에 기본적으로 Kotlin을 사용하지만, 필요에 따라 Java로 전환할 수도 있습니다. 플러그인을 생성한 후, Android Studio에서 Kotlin 플러그인 클래스를 마우스 오른쪽 버튼으로 클릭하고 메뉴에서 “Kotlin 파일을 Java 파일로 변환” 옵션을 선택합니다. Kotlin 프로젝트의 Java로의 마이그레이션에서는 Android Studio의 가이드를 따르십시오.

iOS용 Tauri 플러그인은 Tauri 패키지의 Plugin 클래스를 확장하는 Swift 클래스로 정의됩니다. @objc 속성과 (_invoke: Invoke) 매개변수를 가진 각 함수(예: @objc private func download(_invoke: Invoke) { })는 Rust 또는 JavaScript에서 호출할 수 있습니다.

플러그인은 Swift 패키지로 정의되어 있으므로 Swift의 패키지 관리자를 사용하여 종속성을 관리할 수 있습니다.

플러그인 설정 방법에 대한 자세한 내용은 이전 장 “플러그인 개발”의 플러그인 설정을 참조하십시오.

모바일 플러그인 인스턴스에는 플러그인 설정용 “게터” 커맨드가 있습니다:

import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.TauriPlugin
import app.tauri.annotation.InvokeArg
@InvokeArg
class Config {
var timeout: Int? = 3000
}
@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
private var timeout: Int? = 3000
override fun load(webView: WebView) {
getConfig(Config::class.java).let {
this.timeout = it.timeout
}
}
}

플러그인은 여러 라이프사이클 이벤트에 후크할 수 있습니다:

  • load: 플러그인이 Webview에 로드되었을 때
  • onNewIntent: Android 전용. 액티비티가 재개되었을 때

이전 장의 “플러그인 개발”에는 위 이외의 플러그인 라이프사이클 이벤트가 기재되어 있습니다.

  • 언제: 플러그인이 Webview에 로드되었을 때
  • 목적: 플러그인 초기화 코드 실행
import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.TauriPlugin
@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
override fun load(webView: WebView) {
// 여기서 플러그인 설정 실행
}
}

참고: 이 플러그인은 Android에서만 사용할 수 있습니다.

  • 언제: 액티비티가 재개되었을 때. 자세한 내용은 Activity#onNewIntent를 참조하십시오.
  • 목적: “알림”이 클릭되었을 때나 “딥 링크”에 액세스했을 때 등 애플리케이션 재시작 처리
import android.app.Activity
import android.content.Intent
import app.tauri.annotation.TauriPlugin
@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
override fun onNewIntent(intent: Intent) {
// 새로운 "인텐트" 이벤트 처리
}
}

각 모바일 프로젝트에는 Rust 코드에서 호출 가능한 커맨드를 정의할 수 있는 플러그인 클래스가 있습니다:

import android.app.Activity
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
@Command
fun openCamera(invoke: Invoke) {
val ret = JSObject()
ret.put("path", "/path/to/photo.jpg")
invoke.resolve(ret)
}
}

Kotlin의 suspend 함수를 사용하려면 커스텀 “코루틴” 스코프를 사용해야 합니다.

import android.app.Activity
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
// 데이터 취득을 목적으로 하는 경우 Dispatchers.IO로 변경합니다
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
@Command
fun openCamera(invoke: Invoke) {
scope.launch {
openCameraInner(invoke)
}
}
private suspend fun openCameraInner(invoke: Invoke) {
val ret = JSObject()
ret.put("path", "/path/to/photo.jpg")
invoke.resolve(ret)
}
}

Rust에서 모바일 커맨드를 호출하려면 tauri::plugin::PluginHandle을 사용합니다.

use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use tauri::Runtime;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CameraRequest {
quality: usize,
allow_edit: bool,
}
#[derive(Deserialize)]
pub struct Photo {
path: PathBuf,
}
impl<R: Runtime> <plugin-name;pascal-case><R> {
pub fn open_camera(&self, payload: CameraRequest) -> crate::Result<Photo> {
self
.0
.run_mobile_plugin("openCamera", payload)
.map_err(Into::into)
}
}

인수는 직렬화되어 커맨드에 전달되며, 모바일 플러그인에서 Invoke::parseArgs 함수를 사용하여 구문 분석할 수 있으며, 인수 객체를 설명하는 클래스를 받습니다.

Android에서는 인수가 @app.tauri.annotation.InvokeArg로 어노테이션된 클래스로 정의됩니다. 내부 객체에도 어노테이션을 부여해야 합니다:

import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
@InvokeArg
internal class OpenAppArgs {
lateinit var name: String
var timeout: Int? = null
}
@InvokeArg
internal class OpenArgs {
lateinit var requiredArg: String
var allowEdit: Boolean = false
var quality: Int = 100
var app: OpenAppArgs? = null
}
@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
@Command
fun openCamera(invoke: Invoke) {
val args = invoke.parseArgs(OpenArgs::class.java)
}
}

iOS에서는 인수가 Decodable을 상속하는 클래스로 정의됩니다. 내부 객체도 Decodable 프로토콜을 상속해야 합니다:

class OpenAppArgs: Decodable {
let name: String
var timeout: Int?
}
class OpenArgs: Decodable {
let requiredArg: String
var allowEdit: Bool?
var quality: UInt8?
var app: OpenAppArgs?
}
class ExamplePlugin: Plugin {
@objc public func openCamera(_ invoke: Invoke) throws {
let args = try invoke.parseArgs(OpenArgs.self)
invoke.resolve(["path": "/path/to/photo.jpg"])
}
}

플러그인이 최종 사용자로부터 접근 권한을 필요로 하는 경우, Tauri는 접근 권한 확인 및 요청 프로세스를 간소화합니다.

먼저, 필요한 접근 권한 목록과 코드 내에서 각 그룹을 식별하기 위한 별칭을 정의합니다. 이 처리는 TauriPlugin 어노테이션 내에서 수행됩니다:

@TauriPlugin(
permissions = [
Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "postNotification")
]
)
class ExamplePlugin(private val activity: Activity): Plugin(activity) { }

Tauri는 플러그인에 대한 두 가지 커맨드 checkPermissionsrequestPermissions를 자동으로 구현합니다. 이 두 커맨드는 JavaScript 또는 Rust에서 직접 호출할 수 있습니다.

import { invoke, PermissionState } from '@tauri-apps/api/core'
interface Permissions {
postNotification: PermissionState
}
// 접근 권한 상태 확인
const permission = await invoke<Permissions>('plugin:<plugin-name>|checkPermissions')
if (permission.postNotification === 'prompt-with-rationale') {
// 사용자에게 접근 권한이 필요한 이유에 대한 정보를 표시합니다
}
// 접근 권한 요청
if (permission.postNotification.startsWith('prompt')) {
const state = await invoke<Permissions>('plugin:<plugin-name>|requestPermissions', { permissions: ['postNotification'] })
}

플러그인은 trigger 함수를 사용하여 언제든지 이벤트를 발행할 수 있습니다:

@TauriPlugin
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
override fun load(webView: WebView) {
trigger("load", JSObject())
}
override fun onNewIntent(intent: Intent) {
// 새로운 "인텐트" 이벤트 처리
if (intent.action == Intent.ACTION_VIEW) {
val data = intent.data.toString()
val event = JSObject()
event.put("data", data)
trigger("newIntent", event)
}
}
@Command
fun openCamera(invoke: Invoke) {
val payload = JSObject()
payload.put("open", true)
trigger("camera", payload)
}
}

addPluginListener라는 헬퍼 함수를 사용하여 NPM 패키지에서 헬퍼 함수를 호출할 수 있습니다:

import { addPluginListener, PluginListener } from '@tauri-apps/api/core';
export async function onRequest(
handler: (url: string) => void
): Promise<PluginListener> {
return await addPluginListener(
'<plugin-name>',
'event-name',
handler
);
}

© 2025 Tauri Contributors. CC-BY / MIT