What is the standard method for updating UI elements from a loop calling a web request in Rust using gtk-rs
? I am having trouble updating a label in a UI element to display some data from a web request.
Is there a standard way of doing this? The issues I have been having at the moment have all been related to passing data between threads and as such I am curious to know what is the common way of doing this?
I have a code example here where I get an error stating futures are not Send
. I am using fragile in order to pass a box into the thread.
At the moment I am trying to solve the issue of making my futures Send
however I am not sure that will solve the issue or just leave me with another similar problem.
use gtk::prelude::*;
use gtk::Orientation;
use adw::Application;
use std::thread;
use std::time::Duration;
use fragile;
const APP_ID: &str = "org.currency_trades";
fn main() {
let app = Application::builder().application_id(APP_ID).build();
app.connect_activate(build_ui);
app.run();
}
pub fn build_ui(app: &Application) -> gtk::Box {
let home_box = fragile::Fragile::new(gtk::Box::new(Orientation::Vertical, 15));
thread::spawn(move || {
let box_gbp = gtk::Box::new(Orientation::Horizontal, 250);
let gbp_label = gtk::Label::builder()
.label("GBP")
.margin_top(12)
.margin_start(50)
.build();
let gbp_price_label = gtk::Label::builder()
.label("Uninitialized")
.margin_top(12)
.margin_end(50)
.build();
box_gbp.append(&gbp_label);
box_gbp.append(&gbp_price_label);
home_box.get().append(&box_gbp);
loop {
let runtime = tokio::runtime::Runtime::new().unwrap();
std::thread::sleep(Duration::from_millis(1000));
let _ = runtime.block_on(runtime.spawn(async move {
let gbp_label_request = match reqwest::get("https://www.boredapi.com/api/activity").await {
Ok(label) => label,
Err(_) => panic!("Panic!")
};
let gbp_label = match gbp_label_request.text().await {
Ok(r) => r,
Err(_) => String::from("Unknown")
};
gbp_price_label.set_label(&gbp_label);
}));
}
});
return *home_box.get();
}
The associated error:
error: future cannot be sent between threads safely
--> src/main.rs:89:52
|
89 | let _ = runtime.block_on(runtime.spawn(async move {
| ____________________________________________________^
90 | | let gbp_label_request = match reqwest::get("https://www.boredapi.com/api/activity").await {
91 | | Ok(label) => label,
92 | | Err(_) => panic!("Panic!")
... |
100 | | gbp_price_label.set_label(&gbp_label);
101 | | }));
| |_____________^ future created by async block is not `Send`
|
= help: the trait `Sync` is not implemented for `*mut c_void`
note: captured value is not `Send`
--> src/main.rs:100:17
|
100 | gbp_price_label.set_label(&gbp_label);
| ^^^^^^^^^^^^^^^ has type `gtk4::Label` which is not `Send`
note: required by a bound in `Runtime::spawn`
--> /Users/andy/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs:192:21
|
192 | F: Future + Send + 'static,
| ^^^^ required by this bound in `Runtime::spawn`
For more information about this error, try `rustc --explain E0271`.
There is an example in the gtk-rs event loop documentation that explains how to do this using MainContext::channel.
let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT);
// Connect to "clicked" signal of `button`
button.connect_clicked(move |_| {
let sender = sender.clone();
// The long running operation runs now in a separate thread
thread::spawn(move || {
// Deactivate the button until the operation is done
sender.send(false).expect("Could not send through channel");
let ten_seconds = Duration::from_secs(10);
thread::sleep(ten_seconds);
// Activate the button again
sender.send(true).expect("Could not send through channel");
});
});
// The main loop executes the closure as soon as it receives the message
receiver.attach(
None,
clone!(@weak button => @default-return Continue(false),
move |enable_button| {
button.set_sensitive(enable_button);
Continue(true)
}
),
);