Преглед на файлове

refactor(plugin): add PluginApi and PluginHandle, expose on setup hook (#6291)

Lucas Fernandes Nogueira преди 2 години
родител
ревизия
6aaba83476

+ 5 - 0
.changes/plugin-setup-refactor.md

@@ -0,0 +1,5 @@
+---
+"tauri": major
+---
+
+Changed the plugin setup hook to take a second argument of type `PluginApi`.

+ 1 - 286
core/tauri/src/app.rs

@@ -383,90 +383,6 @@ impl<R: Runtime> AppHandle<R> {
   pub(crate) fn create_proxy(&self) -> R::EventLoopProxy {
     self.runtime_handle.create_proxy()
   }
-
-  /// Initializes an iOS plugin.
-  #[cfg(target_os = "ios")]
-  pub fn initialize_ios_plugin(
-    &self,
-    init_fn: unsafe extern "C" fn(cocoa::base::id),
-  ) -> crate::Result<()> {
-    if let Some(window) = self.windows().values().next() {
-      window.with_webview(move |w| {
-        unsafe { init_fn(w.inner()) };
-      })?;
-    } else {
-      unsafe { init_fn(cocoa::base::nil) };
-    }
-    Ok(())
-  }
-
-  /// Initializes an Android plugin.
-  #[cfg(target_os = "android")]
-  pub fn initialize_android_plugin(
-    &self,
-    plugin_name: &'static str,
-    plugin_identifier: &str,
-    class_name: &str,
-  ) -> crate::Result<()> {
-    use jni::{errors::Error as JniError, objects::JObject, JNIEnv};
-
-    fn initialize_plugin<'a, R: Runtime>(
-      env: JNIEnv<'a>,
-      activity: JObject<'a>,
-      webview: JObject<'a>,
-      runtime_handle: &R::Handle,
-      plugin_name: &'static str,
-      plugin_class: String,
-    ) -> Result<(), JniError> {
-      let plugin_manager = env
-        .call_method(
-          activity,
-          "getPluginManager",
-          format!("()Lapp/tauri/plugin/PluginManager;"),
-          &[],
-        )?
-        .l()?;
-
-      // instantiate plugin
-      let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?;
-      let plugin = env.new_object(
-        plugin_class,
-        "(Landroid/app/Activity;)V",
-        &[activity.into()],
-      )?;
-
-      // load plugin
-      env.call_method(
-        plugin_manager,
-        "load",
-        format!("(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;)V"),
-        &[
-          webview.into(),
-          env.new_string(plugin_name)?.into(),
-          plugin.into(),
-        ],
-      )?;
-
-      Ok(())
-    }
-
-    let plugin_class = format!("{}/{}", plugin_identifier.replace(".", "/"), class_name);
-    let runtime_handle = self.runtime_handle.clone();
-    self
-      .runtime_handle
-      .run_on_android_context(move |env, activity, webview| {
-        let _ = initialize_plugin::<R>(
-          env,
-          activity,
-          webview,
-          &runtime_handle,
-          plugin_name,
-          plugin_class,
-        );
-      });
-
-    Ok(())
-  }
 }
 
 /// APIs specific to the wry runtime.
@@ -876,156 +792,6 @@ macro_rules! shared_app_impl {
         }
         Ok(())
       }
-
-      /// Executes the given plugin mobile method.
-      #[cfg(mobile)]
-      pub fn run_mobile_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
-        &self,
-        plugin: impl AsRef<str>,
-        method: impl AsRef<str>,
-        payload: impl serde::Serialize
-      ) -> crate::Result<Result<T, E>> {
-        #[cfg(target_os = "ios")]
-        {
-          Ok(self.run_ios_plugin(plugin, method, payload))
-        }
-        #[cfg(target_os = "android")]
-        {
-          self.run_android_plugin(plugin, method, payload).map_err(Into::into)
-        }
-      }
-
-      /// Executes the given iOS plugin method.
-      #[cfg(target_os = "ios")]
-      fn run_ios_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
-        &self,
-        plugin: impl AsRef<str>,
-        method: impl AsRef<str>,
-        payload: impl serde::Serialize
-      ) -> Result<T, E> {
-        use std::{os::raw::{c_int, c_char}, ffi::CStr, sync::mpsc::channel};
-
-        let id: i32 = rand::random();
-        let (tx, rx) = channel();
-        PENDING_PLUGIN_CALLS
-          .get_or_init(Default::default)
-          .lock()
-          .unwrap().insert(id, Box::new(move |arg| {
-            tx.send(arg).unwrap();
-          }));
-
-        unsafe {
-          extern "C" fn plugin_method_response_handler(id: c_int, success: c_int, payload: *const c_char) {
-            let payload = unsafe {
-              assert!(!payload.is_null());
-              CStr::from_ptr(payload)
-            };
-
-            if let Some(handler) = PENDING_PLUGIN_CALLS
-              .get_or_init(Default::default)
-              .lock()
-              .unwrap()
-              .remove(&id)
-            {
-              let payload = serde_json::from_str(payload.to_str().unwrap()).unwrap();
-              handler(if success == 1 { Ok(payload) } else { Err(payload) });
-            }
-          }
-
-          crate::ios::run_plugin_method(
-            id,
-            &plugin.as_ref().into(),
-            &method.as_ref().into(),
-            crate::ios::json_to_dictionary(serde_json::to_value(payload).unwrap()),
-            plugin_method_response_handler,
-          );
-        }
-        rx.recv().unwrap()
-          .map(|r| serde_json::from_value(r).unwrap())
-          .map_err(|e| serde_json::from_value(e).unwrap())
-      }
-
-      /// Executes the given Android plugin method.
-      #[cfg(target_os = "android")]
-      fn run_android_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
-        &self,
-        plugin: impl AsRef<str>,
-        method: impl AsRef<str>,
-        payload: impl serde::Serialize
-      ) -> Result<Result<T, E>, jni::errors::Error> {
-        use jni::{
-          errors::Error as JniError,
-          objects::JObject,
-          JNIEnv,
-        };
-
-        fn run<R: Runtime>(
-          id: i32,
-          plugin: String,
-          method: String,
-          payload: serde_json::Value,
-          runtime_handle: &R::Handle,
-          env: JNIEnv<'_>,
-          activity: JObject<'_>,
-        ) -> Result<(), JniError> {
-          let data = crate::jni_helpers::to_jsobject::<R>(env, activity, runtime_handle, payload)?;
-          let plugin_manager = env
-            .call_method(
-              activity,
-              "getPluginManager",
-              "()Lapp/tauri/plugin/PluginManager;",
-              &[],
-            )?
-            .l()?;
-
-          env.call_method(
-            plugin_manager,
-            "runPluginMethod",
-            "(ILjava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;)V",
-            &[
-              id.into(),
-              env.new_string(plugin)?.into(),
-              env.new_string(&method)?.into(),
-              data.into(),
-            ],
-          )?;
-
-          Ok(())
-        }
-
-        let handle = match self.runtime() {
-          RuntimeOrDispatch::Runtime(r) => r.handle(),
-          RuntimeOrDispatch::RuntimeHandle(h) => h,
-          _ => unreachable!(),
-        };
-
-        let id: i32 = rand::random();
-        let plugin = plugin.as_ref().to_string();
-        let method = method.as_ref().to_string();
-        let payload = serde_json::to_value(payload).unwrap();
-        let handle_ = handle.clone();
-
-        let (tx, rx) = std::sync::mpsc::channel();
-        let tx_ = tx.clone();
-        PENDING_PLUGIN_CALLS
-          .get_or_init(Default::default)
-          .lock()
-          .unwrap().insert(id, Box::new(move |arg| {
-            tx.send(Ok(arg)).unwrap();
-          }));
-
-        handle.run_on_android_context(move |env, activity, _webview| {
-          if let Err(e) = run::<R>(id, plugin, method, payload, &handle_, env, activity) {
-            tx_.send(Err(e)).unwrap();
-          }
-        });
-
-        rx.recv().unwrap().map(|response| {
-          response
-            .map(|r| serde_json::from_value(r).unwrap())
-            .map_err(|e| serde_json::from_value(e).unwrap())
-        })
-      }
     }
   };
 }
@@ -1443,7 +1209,7 @@ impl<R: Runtime> Builder<R> {
   ///   }
   ///   pub fn init<R: Runtime>() -> TauriPlugin<R> {
   ///     PluginBuilder::new("window")
-  ///       .setup(|app| {
+  ///       .setup(|app, api| {
   ///         // initialize the plugin here
   ///         Ok(())
   ///       })
@@ -2094,57 +1860,6 @@ impl Default for Builder<crate::Wry> {
   }
 }
 
-#[cfg(mobile)]
-type PendingPluginCallHandler =
-  Box<dyn FnOnce(std::result::Result<serde_json::Value, serde_json::Value>) + Send + 'static>;
-
-#[cfg(mobile)]
-static PENDING_PLUGIN_CALLS: once_cell::sync::OnceCell<
-  std::sync::Mutex<HashMap<i32, PendingPluginCallHandler>>,
-> = once_cell::sync::OnceCell::new();
-
-#[cfg(target_os = "android")]
-#[doc(hidden)]
-pub fn handle_android_plugin_response(
-  env: jni::JNIEnv<'_>,
-  id: i32,
-  success: jni::objects::JString<'_>,
-  error: jni::objects::JString<'_>,
-) {
-  let (payload, is_ok): (serde_json::Value, bool) = match (
-    env
-      .is_same_object(success, jni::objects::JObject::default())
-      .unwrap_or_default(),
-    env
-      .is_same_object(error, jni::objects::JObject::default())
-      .unwrap_or_default(),
-  ) {
-    // both null
-    (true, true) => (serde_json::Value::Null, true),
-    // error null
-    (false, true) => (
-      serde_json::from_str(env.get_string(success).unwrap().to_str().unwrap()).unwrap(),
-      true,
-    ),
-    // success null
-    (true, false) => (
-      serde_json::from_str(env.get_string(error).unwrap().to_str().unwrap()).unwrap(),
-      false,
-    ),
-    // both are set - impossible in the Kotlin code
-    (false, false) => unreachable!(),
-  };
-
-  if let Some(handler) = PENDING_PLUGIN_CALLS
-    .get_or_init(Default::default)
-    .lock()
-    .unwrap()
-    .remove(&id)
-  {
-    handler(if is_ok { Ok(payload) } else { Err(payload) });
-  }
-}
-
 #[cfg(test)]
 mod tests {
   #[test]

+ 1 - 1
core/tauri/src/lib.rs

@@ -240,7 +240,7 @@ macro_rules! android_binding {
 
 #[cfg(all(feature = "wry", target_os = "android"))]
 #[doc(hidden)]
-pub use app::handle_android_plugin_response;
+pub use plugin::handle_android_plugin_response;
 #[cfg(all(feature = "wry", target_os = "android"))]
 #[doc(hidden)]
 pub use tauri_runtime_wry::wry;

+ 354 - 60
core/tauri/src/plugin.rs

@@ -4,6 +4,11 @@
 
 //! The Tauri plugin extension to expand Tauri functionality.
 
+#[cfg(target_os = "android")]
+use crate::{
+  runtime::RuntimeHandle,
+  sealed::{ManagerBase, RuntimeOrDispatch},
+};
 use crate::{
   utils::config::PluginConfig, AppHandle, Invoke, InvokeHandler, PageLoadPayload, RunEvent,
   Runtime, Window,
@@ -12,10 +17,10 @@ use serde::de::DeserializeOwned;
 use serde_json::Value as JsonValue;
 use tauri_macros::default_runtime;
 
-use std::{collections::HashMap, fmt};
+use std::{collections::HashMap, fmt, result::Result as StdResult};
 
 /// The result type of Tauri plugin module.
-pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
+pub type Result<T> = StdResult<T, Box<dyn std::error::Error>>;
 
 /// The plugin interface.
 pub trait Plugin<R: Runtime>: Send {
@@ -59,13 +64,344 @@ pub trait Plugin<R: Runtime>: Send {
   }
 }
 
-type SetupHook<R> = dyn FnOnce(&AppHandle<R>) -> Result<()> + Send;
-type SetupWithConfigHook<R, T> = dyn FnOnce(&AppHandle<R>, T) -> Result<()> + Send;
+type SetupHook<R, C> = dyn FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<()> + Send;
 type OnWebviewReady<R> = dyn FnMut(Window<R>) + Send;
 type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
 type OnPageLoad<R> = dyn FnMut(Window<R>, PageLoadPayload) + Send;
 type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
 
+#[cfg(mobile)]
+type PendingPluginCallHandler =
+  Box<dyn FnOnce(StdResult<serde_json::Value, serde_json::Value>) + Send + 'static>;
+
+#[cfg(mobile)]
+static PENDING_PLUGIN_CALLS: once_cell::sync::OnceCell<
+  std::sync::Mutex<HashMap<i32, PendingPluginCallHandler>>,
+> = once_cell::sync::OnceCell::new();
+
+#[cfg(target_os = "android")]
+#[doc(hidden)]
+pub fn handle_android_plugin_response(
+  env: jni::JNIEnv<'_>,
+  id: i32,
+  success: jni::objects::JString<'_>,
+  error: jni::objects::JString<'_>,
+) {
+  let (payload, is_ok): (serde_json::Value, bool) = match (
+    env
+      .is_same_object(success, jni::objects::JObject::default())
+      .unwrap_or_default(),
+    env
+      .is_same_object(error, jni::objects::JObject::default())
+      .unwrap_or_default(),
+  ) {
+    // both null
+    (true, true) => (serde_json::Value::Null, true),
+    // error null
+    (false, true) => (
+      serde_json::from_str(env.get_string(success).unwrap().to_str().unwrap()).unwrap(),
+      true,
+    ),
+    // success null
+    (true, false) => (
+      serde_json::from_str(env.get_string(error).unwrap().to_str().unwrap()).unwrap(),
+      false,
+    ),
+    // both are set - impossible in the Kotlin code
+    (false, false) => unreachable!(),
+  };
+
+  if let Some(handler) = PENDING_PLUGIN_CALLS
+    .get_or_init(Default::default)
+    .lock()
+    .unwrap()
+    .remove(&id)
+  {
+    handler(if is_ok { Ok(payload) } else { Err(payload) });
+  }
+}
+
+/// A handle to a plugin.
+#[derive(Clone)]
+#[allow(dead_code)]
+pub struct PluginHandle<R: Runtime> {
+  name: &'static str,
+  handle: AppHandle<R>,
+}
+
+impl<R: Runtime> PluginHandle<R> {
+  /// Executes the given mobile method.
+  #[cfg(mobile)]
+  pub fn run_mobile_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
+    &self,
+    method: impl AsRef<str>,
+    payload: impl serde::Serialize,
+  ) -> crate::Result<StdResult<T, E>> {
+    #[cfg(target_os = "ios")]
+    {
+      Ok(self.run_ios_plugin(method, payload))
+    }
+    #[cfg(target_os = "android")]
+    {
+      self.run_android_plugin(method, payload).map_err(Into::into)
+    }
+  }
+
+  /// Executes the given iOS method.
+  #[cfg(target_os = "ios")]
+  fn run_ios_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
+    &self,
+    method: impl AsRef<str>,
+    payload: impl serde::Serialize,
+  ) -> StdResult<T, E> {
+    use std::{
+      ffi::CStr,
+      os::raw::{c_char, c_int},
+      sync::mpsc::channel,
+    };
+
+    let id: i32 = rand::random();
+    let (tx, rx) = channel();
+    PENDING_PLUGIN_CALLS
+      .get_or_init(Default::default)
+      .lock()
+      .unwrap()
+      .insert(
+        id,
+        Box::new(move |arg| {
+          tx.send(arg).unwrap();
+        }),
+      );
+
+    unsafe {
+      extern "C" fn plugin_method_response_handler(
+        id: c_int,
+        success: c_int,
+        payload: *const c_char,
+      ) {
+        let payload = unsafe {
+          assert!(!payload.is_null());
+          CStr::from_ptr(payload)
+        };
+
+        if let Some(handler) = PENDING_PLUGIN_CALLS
+          .get_or_init(Default::default)
+          .lock()
+          .unwrap()
+          .remove(&id)
+        {
+          let payload = serde_json::from_str(payload.to_str().unwrap()).unwrap();
+          handler(if success == 1 {
+            Ok(payload)
+          } else {
+            Err(payload)
+          });
+        }
+      }
+
+      crate::ios::run_plugin_method(
+        id,
+        &self.name.into(),
+        &method.as_ref().into(),
+        crate::ios::json_to_dictionary(serde_json::to_value(payload).unwrap()),
+        plugin_method_response_handler,
+      );
+    }
+    rx.recv()
+      .unwrap()
+      .map(|r| serde_json::from_value(r).unwrap())
+      .map_err(|e| serde_json::from_value(e).unwrap())
+  }
+
+  /// Executes the given Android method.
+  #[cfg(target_os = "android")]
+  fn run_android_plugin<T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned>(
+    &self,
+    method: impl AsRef<str>,
+    payload: impl serde::Serialize,
+  ) -> StdResult<StdResult<T, E>, jni::errors::Error> {
+    use jni::{errors::Error as JniError, objects::JObject, JNIEnv};
+
+    fn run<R: Runtime>(
+      id: i32,
+      plugin: &'static str,
+      method: String,
+      payload: serde_json::Value,
+      runtime_handle: &R::Handle,
+      env: JNIEnv<'_>,
+      activity: JObject<'_>,
+    ) -> StdResult<(), JniError> {
+      let data = crate::jni_helpers::to_jsobject::<R>(env, activity, runtime_handle, payload)?;
+      let plugin_manager = env
+        .call_method(
+          activity,
+          "getPluginManager",
+          "()Lapp/tauri/plugin/PluginManager;",
+          &[],
+        )?
+        .l()?;
+
+      env.call_method(
+        plugin_manager,
+        "runPluginMethod",
+        "(ILjava/lang/String;Ljava/lang/String;Lapp/tauri/plugin/JSObject;)V",
+        &[
+          id.into(),
+          env.new_string(plugin)?.into(),
+          env.new_string(&method)?.into(),
+          data.into(),
+        ],
+      )?;
+
+      Ok(())
+    }
+
+    let handle = match self.handle.runtime() {
+      RuntimeOrDispatch::Runtime(r) => r.handle(),
+      RuntimeOrDispatch::RuntimeHandle(h) => h,
+      _ => unreachable!(),
+    };
+
+    let id: i32 = rand::random();
+    let plugin_name = self.name;
+    let method = method.as_ref().to_string();
+    let payload = serde_json::to_value(payload).unwrap();
+    let handle_ = handle.clone();
+
+    let (tx, rx) = std::sync::mpsc::channel();
+    let tx_ = tx.clone();
+    PENDING_PLUGIN_CALLS
+      .get_or_init(Default::default)
+      .lock()
+      .unwrap()
+      .insert(
+        id,
+        Box::new(move |arg| {
+          tx.send(Ok(arg)).unwrap();
+        }),
+      );
+
+    handle.run_on_android_context(move |env, activity, _webview| {
+      if let Err(e) = run::<R>(id, plugin_name, method, payload, &handle_, env, activity) {
+        tx_.send(Err(e)).unwrap();
+      }
+    });
+
+    rx.recv().unwrap().map(|response| {
+      response
+        .map(|r| serde_json::from_value(r).unwrap())
+        .map_err(|e| serde_json::from_value(e).unwrap())
+    })
+  }
+}
+
+/// Api exposed to the plugin setup hook.
+#[derive(Clone)]
+#[allow(dead_code)]
+pub struct PluginApi<R: Runtime, C: DeserializeOwned> {
+  handle: AppHandle<R>,
+  name: &'static str,
+  config: C,
+}
+
+impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
+  /// Returns the plugin configuration.
+  pub fn config(&self) -> &C {
+    &self.config
+  }
+
+  /// Registers an iOS plugin.
+  #[cfg(target_os = "ios")]
+  pub fn register_ios_plugin(
+    &self,
+    init_fn: unsafe extern "C" fn(cocoa::base::id),
+  ) -> crate::Result<PluginHandle<R>> {
+    if let Some(window) = self.handle.manager.windows().values().next() {
+      window.with_webview(move |w| {
+        unsafe { init_fn(w.inner()) };
+      })?;
+    } else {
+      unsafe { init_fn(cocoa::base::nil) };
+    }
+    Ok(PluginHandle {
+      name: self.name,
+      handle: self.handle.clone(),
+    })
+  }
+
+  /// Registers an Android plugin.
+  #[cfg(target_os = "android")]
+  pub fn register_android_plugin(
+    &self,
+    plugin_identifier: &str,
+    class_name: &str,
+  ) -> crate::Result<PluginHandle<R>> {
+    use jni::{errors::Error as JniError, objects::JObject, JNIEnv};
+
+    fn initialize_plugin<'a, R: Runtime>(
+      env: JNIEnv<'a>,
+      activity: JObject<'a>,
+      webview: JObject<'a>,
+      runtime_handle: &R::Handle,
+      plugin_name: &'static str,
+      plugin_class: String,
+    ) -> StdResult<(), JniError> {
+      let plugin_manager = env
+        .call_method(
+          activity,
+          "getPluginManager",
+          format!("()Lapp/tauri/plugin/PluginManager;"),
+          &[],
+        )?
+        .l()?;
+
+      // instantiate plugin
+      let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?;
+      let plugin = env.new_object(
+        plugin_class,
+        "(Landroid/app/Activity;)V",
+        &[activity.into()],
+      )?;
+
+      // load plugin
+      env.call_method(
+        plugin_manager,
+        "load",
+        format!("(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;)V"),
+        &[
+          webview.into(),
+          env.new_string(plugin_name)?.into(),
+          plugin.into(),
+        ],
+      )?;
+
+      Ok(())
+    }
+
+    let plugin_class = format!("{}/{}", plugin_identifier.replace(".", "/"), class_name);
+    let plugin_name = self.name;
+    let runtime_handle = self.handle.runtime_handle.clone();
+    self
+      .handle
+      .runtime_handle
+      .run_on_android_context(move |env, activity, webview| {
+        let _ = initialize_plugin::<R>(
+          env,
+          activity,
+          webview,
+          &runtime_handle,
+          plugin_name,
+          plugin_class,
+        );
+      });
+
+    Ok(PluginHandle {
+      name: self.name,
+      handle: self.handle.clone(),
+    })
+  }
+}
+
 /// Builds a [`TauriPlugin`].
 ///
 /// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly.
@@ -128,7 +464,7 @@ type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
 ///
 ///   pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
 ///     PluginBuilder::new("example")
-///       .setup(move |app_handle| {
+///       .setup(move |app_handle, api| {
 ///         // use the options here to do stuff
 ///         println!("a: {}, b: {}, c: {}", self.option_a, self.option_b, self.option_c);
 ///
@@ -141,8 +477,7 @@ type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
 pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
   name: &'static str,
   invoke_handler: Box<InvokeHandler<R>>,
-  setup: Option<Box<SetupHook<R>>>,
-  setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
+  setup: Option<Box<SetupHook<R, C>>>,
   js_init_script: Option<String>,
   on_page_load: Box<OnPageLoad<R>>,
   on_webview_ready: Box<OnWebviewReady<R>>,
@@ -156,7 +491,6 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
     Self {
       name,
       setup: None,
-      setup_with_config: None,
       js_init_script: None,
       invoke_handler: Box::new(|_| false),
       on_page_load: Box::new(|_, _| ()),
@@ -236,10 +570,6 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
 
   /// Define a closure that runs when the plugin is registered.
   ///
-  /// This is a convenience function around [setup_with_config], without the need to specify a configuration object.
-  ///
-  /// The closure gets called before the [setup_with_config] closure.
-  ///
   /// # Examples
   ///
   /// ```rust
@@ -253,61 +583,23 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
   ///
   /// fn init<R: Runtime>() -> TauriPlugin<R> {
   /// Builder::new("example")
-  ///   .setup(|app_handle| {
-  ///     app_handle.manage(PluginState::default());
+  ///   .setup(|app, api| {
+  ///     app.manage(PluginState::default());
   ///
   ///     Ok(())
   ///   })
   ///   .build()
   /// }
   /// ```
-  ///
-  /// [setup_with_config]: struct.Builder.html#method.setup_with_config
   #[must_use]
   pub fn setup<F>(mut self, setup: F) -> Self
   where
-    F: FnOnce(&AppHandle<R>) -> Result<()> + Send + 'static,
+    F: FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<()> + Send + 'static,
   {
     self.setup.replace(Box::new(setup));
     self
   }
 
-  /// Define a closure that runs when the plugin is registered, accepting a configuration object set on `tauri.conf.json > plugins > yourPluginName`.
-  ///
-  /// If your plugin is not pulling a configuration object from `tauri.conf.json`, use [setup].
-  ///
-  /// The closure gets called after the [setup] closure.
-  ///
-  /// # Examples
-  ///
-  /// ```rust,no_run
-  /// #[derive(serde::Deserialize)]
-  /// struct Config {
-  ///   api_url: String,
-  /// }
-  ///
-  /// fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R, Config> {
-  ///   tauri::plugin::Builder::<R, Config>::new("api")
-  ///     .setup_with_config(|_app_handle, config| {
-  ///       println!("config: {:?}", config.api_url);
-  ///       Ok(())
-  ///     })
-  ///     .build()
-  /// }
-  ///
-  /// tauri::Builder::default().plugin(init());
-  /// ```
-  ///
-  /// [setup]: struct.Builder.html#method.setup
-  #[must_use]
-  pub fn setup_with_config<F>(mut self, setup_with_config: F) -> Self
-  where
-    F: FnOnce(&AppHandle<R>, C) -> Result<()> + Send + 'static,
-  {
-    self.setup_with_config.replace(Box::new(setup_with_config));
-    self
-  }
-
   /// Callback invoked when the webview performs a navigation to a page.
   ///
   /// # Examples
@@ -420,7 +712,6 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
       app: None,
       invoke_handler: self.invoke_handler,
       setup: self.setup,
-      setup_with_config: self.setup_with_config,
       js_init_script: self.js_init_script,
       on_page_load: self.on_page_load,
       on_webview_ready: self.on_webview_ready,
@@ -435,8 +726,7 @@ pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
   name: &'static str,
   app: Option<AppHandle<R>>,
   invoke_handler: Box<InvokeHandler<R>>,
-  setup: Option<Box<SetupHook<R>>>,
-  setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
+  setup: Option<Box<SetupHook<R, C>>>,
   js_init_script: Option<String>,
   on_page_load: Box<OnPageLoad<R>>,
   on_webview_ready: Box<OnWebviewReady<R>>,
@@ -460,10 +750,14 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
   fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
     self.app.replace(app.clone());
     if let Some(s) = self.setup.take() {
-      (s)(app)?;
-    }
-    if let Some(s) = self.setup_with_config.take() {
-      (s)(app, serde_json::from_value(config)?)?;
+      (s)(
+        app,
+        PluginApi {
+          name: self.name,
+          handle: app.clone(),
+          config: serde_json::from_value(config)?,
+        },
+      )?;
     }
     Ok(())
   }

+ 0 - 10
examples/api/src-tauri/src/lib.rs

@@ -95,16 +95,6 @@ impl AppBuilder {
         #[cfg(debug_assertions)]
         window.open_devtools();
 
-        #[cfg(mobile)]
-        {
-          let response = app.run_mobile_plugin::<serde_json::Value, serde_json::Value>(
-            "sample",
-            "ping",
-            serde_json::Value::default(),
-          );
-          println!("got response: {:?}", response);
-        }
-
         #[cfg(desktop)]
         std::thread::spawn(|| {
           let server = match tiny_http::Server::http("localhost:3003") {

+ 15 - 9
examples/api/src-tauri/tauri-plugin-sample/src/lib.rs

@@ -1,9 +1,8 @@
 use tauri::{
-  plugin::{Builder, TauriPlugin},
-  Runtime,
+  plugin::{Builder, PluginHandle, TauriPlugin},
+  Manager, Runtime,
 };
 
-const PLUGIN_NAME: &str = "sample";
 #[cfg(target_os = "android")]
 const PLUGIN_IDENTIFIER: &str = "com.plugin.sample";
 
@@ -12,13 +11,20 @@ extern "C" {
   fn init_plugin_sample(webview: tauri::cocoa::base::id);
 }
 
+pub struct SamplePlugin<R: Runtime>(PluginHandle<R>);
+
 pub fn init<R: Runtime>() -> TauriPlugin<R> {
-  Builder::new(PLUGIN_NAME)
-    .setup(|app| {
-      #[cfg(target_os = "android")]
-      app.initialize_android_plugin(PLUGIN_NAME, PLUGIN_IDENTIFIER, "ExamplePlugin")?;
-      #[cfg(target_os = "ios")]
-      app.initialize_ios_plugin(init_plugin_sample)?;
+  Builder::new("sample")
+    .setup(|app, api| {
+      #[cfg(any(target_os = "android", target_os = "ios"))]
+      {
+        #[cfg(target_os = "android")]
+        let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?;
+        #[cfg(target_os = "ios")]
+        let handle = api.register_ios_plugin(init_plugin_sample)?;
+        app.manage(SamplePlugin(handle));
+      }
+
       Ok(())
     })
     .build()

+ 2 - 2
tooling/cli/src/plugin/android.rs

@@ -106,9 +106,9 @@ tauri-build = "{}"
         r#"
 pub fn init<R: Runtime>() -> TauriPlugin<R> {{
   Builder::new("{name}")
-    .setup(|app| {{
+    .setup(|app, api| {{
       #[cfg(target_os = "android")]
-      app.initialize_android_plugin("{name}", "{identifier}, "ExamplePlugin")?;
+      let handle = api.register_android_plugin("{identifier}, "ExamplePlugin")?;
       Ok(())
     }})
     .build()

+ 5 - 1
tooling/cli/src/plugin/init.rs

@@ -11,7 +11,7 @@ use anyhow::Context;
 use clap::Parser;
 use dialoguer::Input;
 use handlebars::{to_json, Handlebars};
-use heck::{AsKebabCase, ToKebabCase, ToSnakeCase};
+use heck::{AsKebabCase, ToKebabCase, ToPascalCase, ToSnakeCase};
 use include_dir::{include_dir, Dir};
 use log::warn;
 use std::{
@@ -193,6 +193,10 @@ pub fn plugin_name_data(data: &mut BTreeMap<&'static str, serde_json::Value>, pl
     "plugin_name_snake_case",
     to_json(plugin_name.to_snake_case()),
   );
+  data.insert(
+    "plugin_name_pascal_case",
+    to_json(plugin_name.to_pascal_case()),
+  );
 }
 
 pub fn crates_metadata() -> Result<VersionMetadata> {

+ 13 - 8
tooling/cli/templates/plugin/src/lib.rs

@@ -5,7 +5,7 @@
 use serde::{ser::Serializer, Serialize};
 use tauri::{
   command,
-  plugin::{Builder, TauriPlugin},
+  plugin::{Builder, PluginHandle, TauriPlugin},
   AppHandle, Manager, Runtime, State, Window,
 };
 
@@ -13,7 +13,6 @@ use std::{collections::HashMap, sync::Mutex};
 
 type Result<T> = std::result::Result<T, Error>;
 
-const PLUGIN_NAME: &str = "{{ plugin_name }}";
 #[cfg(target_os = "android")]
 const PLUGIN_IDENTIFIER: &str = "{{ android_package_id }}";
 
@@ -40,6 +39,8 @@ impl Serialize for Error {
 #[derive(Default)]
 struct MyState(Mutex<HashMap<String, String>>);
 
+pub struct {{ plugin_name_pascal_case }}Plugin<R: Runtime>(PluginHandle<R>);
+
 #[command]
 async fn execute<R: Runtime>(
   _app: AppHandle<R>,
@@ -52,14 +53,18 @@ async fn execute<R: Runtime>(
 
 /// Initializes the plugin.
 pub fn init<R: Runtime>() -> TauriPlugin<R> {
-  Builder::new(PLUGIN_NAME)
+  Builder::new("{{ plugin_name }}")
     .invoke_handler(tauri::generate_handler![execute])
-    .setup(|app| {
+    .setup(|app, api| {
       // initialize mobile plugins
-      #[cfg(target_os = "android")]
-      app.initialize_android_plugin(PLUGIN_NAME, PLUGIN_IDENTIFIER, "ExamplePlugin")?;
-      #[cfg(target_os = "ios")]
-      app.initialize_ios_plugin(init_plugin_{{ plugin_name }})?;
+      #[cfg(any(target_os = "android", target_os = "ios"))]
+      {
+        #[cfg(target_os = "android")]
+        let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?;
+        #[cfg(target_os = "ios")]
+        let handle = api.register_ios_plugin(init_plugin_{{ plugin_name }})?;
+        app.manage({{ plugin_name_pascal_case }}Plugin(handle));
+      }
 
       // manage state so it is accessible by the commands
       app.manage(MyState::default());