Loading...
Loading...
Guides developers through Tauri v2 event system for calling frontend from Rust, covering emit functions, event payloads, IPC channels, and JavaScript evaluation for bi-directional Rust-frontend communication.
npx skill4agent add dchuk/claude-code-tauri-skills calling-frontend-from-tauri-rustuse tauri::{AppHandle, Emitter, Manager, Listener, EventTarget};
use serde::Serialize;import { listen, once, emit } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';AppHandle::emit()use tauri::{AppHandle, Emitter};
#[tauri::command]
fn download(app: AppHandle, url: String) {
app.emit("download-started", &url).unwrap();
for progress in [1, 15, 50, 80, 100] {
app.emit("download-progress", progress).unwrap();
}
app.emit("download-finished", &url).unwrap();
}emit_to()use tauri::{AppHandle, Emitter};
#[tauri::command]
fn login(app: AppHandle, user: String, password: String) {
let authenticated = user == "tauri-apps" && password == "tauri";
let result = if authenticated { "loggedIn" } else { "invalidCredentials" };
app.emit_to("login", "login-result", result).unwrap();
}emit_filter()use tauri::{AppHandle, Emitter, EventTarget};
#[tauri::command]
fn open_file(app: AppHandle, path: std::path::PathBuf) {
app.emit_filter("open-file", path, |target| match target {
EventTarget::WebviewWindow { label } => label == "main" || label == "file-viewer",
_ => false,
}).unwrap();
}SerializeCloneuse serde::Serialize;
use tauri::{AppHandle, Emitter};
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct DownloadProgress {
download_id: usize,
chunk_length: usize,
total_size: usize,
}
#[tauri::command]
fn download(app: AppHandle, url: String) {
app.emit("download-progress", DownloadProgress {
download_id: 1,
chunk_length: 150,
total_size: 1000,
}).unwrap();
}import { listen } from '@tauri-apps/api/event';
type DownloadStarted = {
url: string;
downloadId: number;
contentLength: number;
};
listen<DownloadStarted>('download-started', (event) => {
console.log(`downloading ${event.payload.contentLength} bytes from ${event.payload.url}`);
});import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWebview = getCurrentWebviewWindow();
appWebview.listen<string>('logged-in', (event) => {
localStorage.setItem('session-token', event.payload);
});import { listen, once } from '@tauri-apps/api/event';
// Unlisten to prevent memory leaks
const unlisten = await listen('download-started', (event) => {
console.log('download started');
});
unlisten(); // Stop listening when done
// Listen once for one-time events
once('app-ready', (event) => {
console.log('App is ready:', event.payload);
});use tauri::{Listener, Manager};
tauri::Builder::default()
.setup(|app| {
// Global listener
app.listen("download-started", |event| {
println!("event received: {}", event.payload());
});
// Webview-specific listener
let webview = app.get_webview_window("main").unwrap();
webview.listen("logged-in", |event| {
println!("User logged in: {}", event.payload());
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application")use tauri::Listener;
// Store event ID to unlisten later
let event_id = app.listen("download-started", |event| {
println!("download started");
});
app.unlisten(event_id);
// Conditional unlisten
let handle = app.handle().clone();
app.listen("status-changed", move |event| {
if event.payload() == "\"ready\"" {
handle.unlisten(event.id());
}
});
// Listen once
app.once("ready", |event| {
println!("app is ready: {}", event.payload());
});use tauri::{AppHandle, ipc::Channel};
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", tag = "event", content = "data")]
enum DownloadEvent<'a> {
#[serde(rename_all = "camelCase")]
Started { url: &'a str, download_id: usize, content_length: usize },
#[serde(rename_all = "camelCase")]
Progress { download_id: usize, chunk_length: usize },
#[serde(rename_all = "camelCase")]
Finished { download_id: usize },
}
#[tauri::command]
fn download(app: AppHandle, url: String, on_event: Channel<DownloadEvent>) {
on_event.send(DownloadEvent::Started {
url: &url,
download_id: 1,
content_length: 1000,
}).unwrap();
for _ in 0..10 {
on_event.send(DownloadEvent::Progress {
download_id: 1,
chunk_length: 100,
}).unwrap();
}
on_event.send(DownloadEvent::Finished { download_id: 1 }).unwrap();
}import { invoke, Channel } from '@tauri-apps/api/core';
type DownloadEvent =
| { event: 'started'; data: { url: string; downloadId: number; contentLength: number } }
| { event: 'progress'; data: { downloadId: number; chunkLength: number } }
| { event: 'finished'; data: { downloadId: number } };
const onEvent = new Channel<DownloadEvent>();
onEvent.onmessage = (message) => {
switch (message.event) {
case 'started':
console.log(`Download started: ${message.data.url}`);
break;
case 'progress':
console.log(`Progress: ${message.data.chunkLength} bytes`);
break;
case 'finished':
console.log('Download complete!');
break;
}
};
await invoke('download', { url: 'https://example.com/file.json', onEvent });use tauri::Manager;
tauri::Builder::default()
.setup(|app| {
let webview = app.get_webview_window("main").unwrap();
webview.eval("console.log('hello from Rust')")?;
Ok(())
})use tauri::Manager;
#[tauri::command]
fn notify_frontend(app: tauri::AppHandle, message: String) {
if let Some(webview) = app.get_webview_window("main") {
let script = format!("window.showNotification('{}')", message);
webview.eval(&script).unwrap();
}
}# Cargo.toml
[dependencies]
serialize-to-javascript = "0.1"use serialize_to_javascript::Serialized;
use tauri::Manager;
#[derive(serde::Serialize)]
struct AppState { user: String, logged_in: bool }
#[tauri::command]
fn sync_state(app: tauri::AppHandle) {
let state = AppState { user: "john".to_string(), logged_in: true };
if let Some(webview) = app.get_webview_window("main") {
let serialized = Serialized::new(&state, &Default::default()).into_string();
webview.eval(&format!("window.updateState({})", serialized)).unwrap();
}
}| Method | Use Case | Performance |
|---|---|---|
Events ( | Multi-consumer, broadcast | Moderate |
| Channels | High-throughput streaming, single consumer | High |
| JS Eval | Direct DOM manipulation, no response needed | Low overhead |
use tauri::{AppHandle, Emitter};
use serde::Serialize;
use std::path::PathBuf;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct FileChange { path: String, event_type: String }
#[tauri::command]
fn watch_directory(app: AppHandle, path: PathBuf) {
std::thread::spawn(move || {
loop {
app.emit("file-changed", FileChange {
path: path.to_string_lossy().to_string(),
event_type: "modified".to_string(),
}).unwrap();
std::thread::sleep(std::time::Duration::from_secs(5));
}
});
}import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
type FileChange = { path: string; eventType: string };
await invoke('watch_directory', { path: '/some/directory' });
const unlisten = await listen<FileChange>('file-changed', (event) => {
console.log(`File ${event.payload.eventType}: ${event.payload.path}`);
});
// Cleanup when component unmounts: unlisten();