tauri icon indicating copy to clipboard operation
tauri copied to clipboard

[bug] Kotlin can't Trigger events to Rust or JS

Open oddpxl opened this issue 8 months ago • 4 comments

Describe the bug

When I add a listener for Trigger in Kotlin... ( https://v2.tauri.app/develop/plugins/develop-mobile/#plugin-events )

I get a permission error ( with seemingly no possible way to give this permission in default.json ) "Error setting up listeners: Command plugin:theplug|registerListener not allowed by ACL"

Also - it seems not possible for Kotlin to trigger a function in Rust ? ( commands.rs or otherwise )

Reproduction

1 - create an Android plugin 2 - add some commands in comands.rs / mobile.rs / desktop.rs 3 - add a Kotlin Trigger that sends a message to JS

We get this error Error setting up listeners: Command plugin:theplug|registerListener not allowed by ACL

Expected behavior

The provided example should work https://v2.tauri.app/develop/plugins/develop-mobile/#plugin-events

Also - it would be great if we could app.emit from Kotlin - then listen in Rust / JS - it seems not possible ?

Full tauri info output

Error setting up listeners: Command plugin:theplug|registerListener not allowed by ACL

Stack trace


Additional context

No response

oddpxl avatar Mar 19 '25 00:03 oddpxl

I've been going through the same problem. mine is a bit different though I'm not getting any errors it's just silent failure.

Kotlin code:

package com.plugin.rt.test

import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@InvokeArg
class PingArgs {
    var value: String? = null
}

@TauriPlugin
class ExamplePlugin(private val activity: Activity) : Plugin(activity) {
    private val implementation = Example()

    @Command
    fun ping(invoke: Invoke) {
        val args = invoke.parseArgs(PingArgs::class.java)

        val ret = JSObject()
        ret.put("value", implementation.pong(args.value ?: "default value :("))
        invoke.resolve(ret)
    }

    override fun load(webView: WebView) {
        startPeriodicTrigger()
    }

    private var timerJob: Job? = null

    fun startPeriodicTrigger() {
        timerJob =
                CoroutineScope(Dispatchers.IO).launch {
                    while (true) {
                        val ret =
                                JSObject().apply {
                                    put("message", "Test message")
                                    put("timestamp", System.currentTimeMillis())
                                }
                        println("Hello corutine msg called")
                        trigger("custom-event-name", ret)
                        delay(3000) // Wait 3 seconds
                    }
                }
    }
}

Rust listener:

use tauri::Listener;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
  tauri::Builder::default()
    .plugin(tauri_plugin_rt_test::init())
    .setup(|app| {
      let handle = app.handle().clone();
      handle.clone().listen_any("custom-event-name", move |e| {
        println!("Got mock msg: {:?}", e);
        // handle.exit(0);
      });
      Ok(())
    })
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

html:

<html>
    <head>
        <script defer>
            function my_log(input) {
                const newDiv = document.createElement("div");
                newDiv.textContent = JSON.stringify(input, null, 2);
                document.body.appendChild(newDiv);
            }

            async function main() {
                try {
                    await window.__TAURI__.event.listen(
                        "custom-event-name",
                        (e) => {
                            my_log(e);
                        },
                    );
                    my_log("Valid listener mounted");
                } catch (e) {
                    my_log(e);
                }
            }

            main();
        </script>
    </head>
    <body>
        <div>Plugin example</div>
    </body>
</html>

If I emit event from Rust it's just expected behavior. Emitting from Kotlin is just as if it's doing nothing.

Does anyone know any other way to extract data from native Kotlin code into Rust/JS?

ghost avatar Mar 19 '25 11:03 ghost

I create a full example on this issue: https://github.com/F8RZD/tauri-kotlin-problem

ghost avatar Mar 19 '25 11:03 ghost

This is undocumented, but you have to add two built-in commands registerListener and unregister_listener in your commands list and add them to the default permissions.

diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/build.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/build.rs
index e6bab73..e41f12a 100644
--- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/build.rs
+++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/build.rs
@@ -13,6 +13,8 @@ const COMMANDS: &[&str] = &[
     "get_all_voices",
     "get_granularities",
     "get_speaking_lang",
+    "registerListener",
+    "unregister_listener",
 ];

 fn main() {
--- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/permissions/default.toml
+++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/permissions/default.toml
@@ -15,4 +15,6 @@ permissions = [
   "allow-get-all-voices",
   "allow-get-granularities",
   "allow-get-speaking-lang",
+  "allow-registerListener",
+  "allow-unregister-listener",
 ]

chrox avatar Jun 11 '25 08:06 chrox

@FabianLars Are there any plans to standardize the naming convention between registerListener and unregister_listener to make them consistent?

UPDATE: I see #12486 is on this. I will watch on it.

chrox avatar Jun 11 '25 08:06 chrox

@chrox Thank you so much for this comment!

I was following the code of the official notification plugin and it uses register_listener. How come the hyphenated/snakecase version works there? Or does it (I'm not using that plugin, yet)?

jgonera avatar Aug 20 '25 00:08 jgonera

@chrox Thank you so much for this comment!

I was following the code of the official notification plugin and it uses register_listener. How come the hyphenated/snakecase version works there? Or does it (I'm not using that plugin, yet)?

I'm not using the notification plugin but from the tauri core API registerListener is used instead of register_listener.

async function addPluginListener<T>(
  plugin: string,
  event: string,
  cb: (payload: T) => void
): Promise<PluginListener> {
  const handler = new Channel<T>(cb)
  return invoke(`plugin:${plugin}|registerListener`, { event, handler }).then(
    () => new PluginListener(plugin, event, handler.id)
  )
}

chrox avatar Aug 20 '25 02:08 chrox

This is undocumented, but you have to add two built-in commands registerListener and unregister_listener in your commands list and add them to the default permissions.

diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/build.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/build.rs index e6bab73..e41f12a 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/build.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/build.rs @@ -13,6 +13,8 @@ const COMMANDS: &[&str] = &[ "get_all_voices", "get_granularities", "get_speaking_lang",

  • "registerListener",
  • "unregister_listener", ];

fn main() { --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/permissions/default.toml +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/permissions/default.toml @@ -15,4 +15,6 @@ permissions = [ "allow-get-all-voices", "allow-get-granularities", "allow-get-speaking-lang",

  • "allow-registerListener",
  • "allow-unregister-listener", ]

For those who use Tauri 2.9.0+, you might need add allow-register-listener permission instead of allow-registerListener, and add registerListener command instead of registerListener after https://github.com/tauri-apps/tauri/pull/14132. Unfortunately this is not documented neither at https://tauri.app/develop/plugins/develop-mobile/#plugin-events nor at https://tauri.app/reference/javascript/api/namespacecore/#addpluginlistener.

chrox avatar Oct 28 '25 15:10 chrox