Skip to content

Commit 457fc86

Browse files
committed
examples: Added a little example that shows how to use asynchronous http requests with Slint
This is a little "stock ticker" that when manually refreshed uses async Rust and Python APIs to request new prices from a website and update a Slint model. In Rust, this demonstrates the use of `slint::spawn_local()` and in Python the use of our asyncio integration.
1 parent 669272f commit 457fc86

File tree

7 files changed

+221
-0
lines changed

7 files changed

+221
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ members = [
3434
'examples/mcu-board-support',
3535
'examples/mcu-embassy',
3636
'examples/uefi-demo',
37+
'examples/async-io',
3738
'demos/weather-demo',
3839
'demos/usecases/rust',
3940
'helper_crates/const-field-offset',

examples/async-io/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright © SixtyFPS GmbH <[email protected]>
2+
# SPDX-License-Identifier: MIT
3+
4+
[package]
5+
name = "stockticker"
6+
version = "1.14.0"
7+
authors = ["Slint Developers <[email protected]>"]
8+
edition = "2021"
9+
publish = false
10+
license = "MIT"
11+
12+
[[bin]]
13+
path = "main.rs"
14+
name = "stockticker"
15+
16+
[dependencies]
17+
slint = { path = "../../api/rs/slint" }
18+
async-compat = { version = "0.2.4" }
19+
reqwest = { version = "0.12", features = ["json"] }
20+
serde = "1.0"
21+
serde_json = "1.0"

examples/async-io/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Example demonstrating asynchronous API usage with Slint
2+
3+
This example demonstrates how to use asynchronous I/O, by means of issuing HTTP GET requests, within the Slint event loop.
4+
5+
The http GET requests fetch the closing prices of a few publicly traded stocks, and the result is show in the simple Slint UI.
6+
7+
# Rust
8+
9+
The Rust version is contained in [`main.rs`](./main.rs). It uses the `rewquest` crate to establish a network connection and issue the HTTP get requests, using Rusts `async` functions. These are run inside a future run with `slint::spawn_local()`, where we can await for the result of the network request and update the UI directly - as we're being run in the UI thread.
10+
11+
Run the Rust version via `cargo run -p stockticker`.
12+
13+
# Python
14+
15+
The Python version is contained in [`main.py`](./main.py). It uses the `aiohttp` library to establish a network connection and issue the HTTP get requests, using Python's `asyncio` library. The entire request is started from within the `refresh` function that's marked to be `async` and connected to the `refresh` callback in `stockticker.slint`. Slint detects that the callback is async in Python and runs it as a new task.
16+
17+
Run the Python version via `uv run main.py` in the `examples/async-io` directory.

examples/async-io/main.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright © SixtyFPS GmbH <[email protected]>
2+
# SPDX-License-Identifier: MIT
3+
4+
import slint
5+
import asyncio
6+
import aiohttp
7+
8+
Symbol = slint.loader.stockticker.Symbol
9+
10+
11+
async def refresh_stocks(model: slint.ListModel[Symbol]) -> None:
12+
STOOQ_URL = "https://stooq.com/q/l/?s={symbols}&f=sd2t2ohlcvn&h&e=json"
13+
url = STOOQ_URL.format(symbols="+".join([symbol.name for symbol in model]))
14+
async with aiohttp.ClientSession() as session:
15+
async with session.get(url) as resp:
16+
json = await resp.json()
17+
json_symbols = json["symbols"]
18+
for row, symbol in enumerate(model):
19+
data_for_symbol = next(
20+
(sym for sym in json_symbols if sym["symbol"] == symbol.name), None
21+
)
22+
if data_for_symbol:
23+
symbol.price = data_for_symbol["close"]
24+
model.set_row_data(row, symbol)
25+
26+
27+
class MainWindow(slint.loader.stockticker.MainWindow):
28+
def __init__(self):
29+
super().__init__()
30+
self.stocks = slint.ListModel(
31+
[Symbol(name=name, price=0.0) for name in ["AAPL.US", "MSFT.US", "AMZN.US"]]
32+
)
33+
34+
@slint.callback
35+
async def refresh(self):
36+
await refresh_stocks(self.stocks)
37+
38+
39+
async def main() -> None:
40+
main_window = MainWindow()
41+
main_window.refresh()
42+
main_window.show()
43+
44+
45+
slint.run_event_loop(main())

examples/async-io/main.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright © SixtyFPS GmbH <[email protected]>
2+
// SPDX-License-Identifier: MIT
3+
4+
use slint::Model;
5+
6+
use async_compat::Compat;
7+
8+
slint::slint! {
9+
export { MainWindow, Symbol } from "stockticker.slint";
10+
}
11+
12+
#[derive(serde::Deserialize, Debug, Clone)]
13+
struct JsonSymbol {
14+
symbol: String,
15+
close: f32,
16+
}
17+
18+
#[derive(serde::Deserialize, Debug, Clone)]
19+
struct JsonSymbols {
20+
symbols: Vec<JsonSymbol>,
21+
}
22+
23+
async fn refresh_stocks(model: slint::ModelRc<Symbol>) {
24+
let url = format!(
25+
"https://stooq.com/q/l/?s={}&f=sd2t2ohlcvn&h&e=json",
26+
model.iter().map(|symbol| symbol.name.clone()).collect::<Vec<_>>().join("+")
27+
);
28+
29+
let response = match reqwest::get(url).await {
30+
Ok(response) => response,
31+
Err(err) => {
32+
eprintln!("Error fetching update: {err}");
33+
return;
34+
}
35+
};
36+
37+
let json_symbols: JsonSymbols = match response.json().await {
38+
Ok(json) => json,
39+
Err(err) => {
40+
eprintln!("Error decoding json response: {err}");
41+
return;
42+
}
43+
};
44+
45+
for row in 0..model.row_count() {
46+
let mut symbol = model.row_data(row).unwrap();
47+
let Some(json_symbol) = json_symbols.symbols.iter().find(|s| *s.symbol == *symbol.name)
48+
else {
49+
continue;
50+
};
51+
symbol.price = json_symbol.close;
52+
model.set_row_data(row, symbol);
53+
}
54+
}
55+
56+
fn main() -> Result<(), slint::PlatformError> {
57+
let main_window = MainWindow::new()?;
58+
59+
let model = slint::VecModel::from_slice(&[
60+
Symbol { name: "AAPL.US".into(), price: 0.0 },
61+
Symbol { name: "MSFT.US".into(), price: 0.0 },
62+
Symbol { name: "AMZN.US".into(), price: 0.0 },
63+
]);
64+
65+
main_window.set_stocks(model.clone().into());
66+
67+
main_window.show()?;
68+
69+
slint::spawn_local(Compat::new(refresh_stocks(model.clone()))).unwrap();
70+
71+
main_window.on_refresh(move || {
72+
slint::spawn_local(Compat::new(refresh_stocks(model.clone()))).unwrap();
73+
});
74+
75+
main_window.run()
76+
}

examples/async-io/pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright © SixtyFPS GmbH <[email protected]>
2+
# SPDX-License-Identifier: MIT
3+
4+
[project]
5+
name = "python"
6+
version = "1.14.0"
7+
description = "Slint Stock Ticker Example for Python"
8+
readme = "README.md"
9+
requires-python = ">=3.12"
10+
dependencies = ["aiohttp>=3.12.15", "slint"]
11+
12+
[tool.uv.sources]
13+
slint = { path = "../../api/python/slint", editable = true }
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright © SixtyFPS GmbH <[email protected]>
2+
// SPDX-License-Identifier: MIT
3+
4+
import { Button, VerticalBox, HorizontalBox } from "std-widgets.slint";
5+
6+
export struct Symbol {
7+
name: string,
8+
price: float,
9+
}
10+
11+
export component MainWindow inherits Window {
12+
// The symbols and prices here are just to show something in the live-preview. They'll be replaced
13+
// with fresh data on start-up.
14+
in-out property <[Symbol]> stocks: [
15+
{
16+
name: "AAPL.US",
17+
price: 237.96,
18+
},
19+
{
20+
name: "MSFT.US",
21+
price: 509.24,
22+
},
23+
{ name: "AMZN.US", price: 232.94 },
24+
];
25+
callback refresh();
26+
27+
VerticalBox {
28+
padding: 10px;
29+
30+
for stock in root.stocks: HorizontalBox {
31+
alignment: start;
32+
Text {
33+
text: stock.name;
34+
}
35+
36+
Text {
37+
text: "$\{stock.price.to-fixed(2)}";
38+
}
39+
}
40+
41+
Button {
42+
text: @tr("Refresh");
43+
clicked => {
44+
root.refresh();
45+
}
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)