Expand description
§Tokio Integration
Freya has its own async runtime, but many Rust ecosystem crates depend on Tokio for things like HTTP clients, database drivers, etc.
§Setting up the Runtime
#[tokio::main] is completely discouraged because it messes the event loop, instead create a Tokio runtime in main()
and enter it before launching Freya.
The enter()
guard makes Tokio APIs available on the current thread.
use tokio::runtime::Builder;
fn main() {
let rt = Builder::new_multi_thread().enable_all().build().unwrap();
// Enter the Tokio runtime so its APIs (channels, timers, etc.) work.
let _rt = rt.enter();
launch(LaunchConfig::new().with_window(WindowConfig::new(app)))
}The _rt guard must remain alive for the duration of the program.
Binding it to a variable in main() achieves this.
§What you can use
With the Tokio context active, you can use:
tokio::syncchannels (mpsc,watch,broadcast,oneshot, etc.)tokio::timeutilities (sleep,interval,timeout)- Any ecosystem crate that requires a Tokio runtime (e.g.
reqwest,sqlx,tonic)
These work well with Freya’s built-in spawn():
let mut count = use_state(|| 0);
use_future(move || {
async move {
// tokio::time::sleep works because the Tokio context is active
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
count.set(42);
}
});§Caveat about tokio::spawn
Freya’s reactivity model is single-threaded, which means you cannot update
e.g state from another thread, and thus using tokio::spawn is simply
not possible due to the Sync and Send bounds.
You may tokio::spawn for background work that does not interact with
components. For anything that updates the UI, use
Freya’s spawn() instead.
§Watch Channels with use_track_watcher
You can easily subscribe to tokio watch channels using use_track_watcher
hook from the sdk feature:
[dependencies]
freya = { version = "...", features = ["sdk"] }
tokio = { version = "1", features = ["sync"] }The sender can live on any thread/task. use_track_watcher re-renders the
component whenever the watch value changes:
fn main() {
let rt = Builder::new_multi_thread().enable_all().build().unwrap();
let _rt = rt.enter();
let (tx, rx) = watch::channel(0);
launch(
LaunchConfig::new()
.with_future(move |_| async move {
let mut interval = tokio::time::interval(Duration::from_secs(1));
interval.tick().await;
let mut i = 0;
loop {
interval.tick().await;
i += 1;
let _ = tx.send(i);
}
})
.with_window(WindowConfig::new_app(MyApp { rx })),
)
}
struct MyApp {
rx: watch::Receiver<i32>,
}
impl App for MyApp {
fn render(&self) -> impl IntoElement {
// Re-renders this component whenever the watched value changes.
use_track_watcher(&self.rx);
rect()
.expanded()
.center()
.child(format!("Latest value is {}", *self.rx.borrow()))
}
}§Examples
integration_tokio_spawn.rs: Minimal Tokio runtime setupintegration_tokio_watch.rs: Reactive watch channel withuse_track_watcher