Custom Command using perform
In this example, we will create an app that fetches your current IP address by making an API call with our own custom command..
Dependencies
As you see, we have two dependencies in our project.
One of them is reqwest. We use reqwest to make the API call.
The other one is iced.
Since this is a guide for iced, that should not wonder you.
But as you see, we added the tokio
feature.
This lets iced use tokio as part of the runtime as needed for reqwest.
[dependencies]
iced = {version="0.12.1", features = ["tokio"]}
reqwest = "0.11.24"
Making the api request
At first, we define what our command will do.
For that, we are creating an async function that makes an async get request to an API that provides the public IP.
use iced::{Application, Command};
#[derive(Debug, Clone)]
enum Message {
Refetch,
CurrentIp(String),
}
struct App {
ip: String,
}
impl Application for App {
type Message = Message;
type Theme = iced::Theme;
type Flags = ();
type Executor = iced::executor::Default;
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
(App { ip: String::new() }, Command::none())
}
fn title(&self) -> String {
String::new()
}
fn view(&self) -> iced::Element<'_, Self::Message, Self::Theme, iced::Renderer> {
iced::widget::column![
iced::widget::text(&self.ip),
iced::widget::button("Start Command").on_press(Message::Refetch)
]
.into()
}
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
println!("update");
match message {
Message::Refetch => return Command::perform(fetch_ip(), Message::CurrentIp),
Message::CurrentIp(text) => {
self.ip = text;
}
}
Command::none()
}
}
async fn fetch_ip() -> String {
println!("fetch_ip");
reqwest::get("https://api.seeip.org")
.await
.unwrap()
.text()
.await
.unwrap()
}
fn main() {
App::run(iced::Settings::default()).expect("Application raised an error");
}
Tip: If you have something that is not async but synchronous and will block your application like a heavy computation, you can use
tokio::spawn_blocking
in a command or subscription to run a closure on a thread where blocking is acceptable.
Starting/Creating the command
In the update function we return Command::none()
or our custom command depending on the message.
If the Message is Message::CurrentIp
we change our state, if it is Message::Refetch
we return our command.
use iced::{Application, Command};
#[derive(Debug, Clone)]
enum Message {
Refetch,
CurrentIp(String),
}
struct App {
ip: String,
}
impl Application for App {
type Message = Message;
type Theme = iced::Theme;
type Flags = ();
type Executor = iced::executor::Default;
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
(App { ip: String::new() }, Command::none())
}
fn title(&self) -> String {
String::new()
}
fn view(&self) -> iced::Element<'_, Self::Message, Self::Theme, iced::Renderer> {
iced::widget::column![
iced::widget::text(&self.ip),
iced::widget::button("Start Command").on_press(Message::Refetch)
]
.into()
}
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
println!("update");
match message {
Message::Refetch => return Command::perform(fetch_ip(), Message::CurrentIp),
Message::CurrentIp(text) => {
self.ip = text;
}
}
Command::none()
}
}
async fn fetch_ip() -> String {
println!("fetch_ip");
reqwest::get("https://api.seeip.org")
.await
.unwrap()
.text()
.await
.unwrap()
}
fn main() {
App::run(iced::Settings::default()).expect("Application raised an error");
}
To create our custom command, we use the Command::perform
function.
It takes a future, in this case our fetch_ip
function, and a closure that converts the returned value of the future into a massage.
use iced::{Application, Command};
#[derive(Debug, Clone)]
enum Message {
Refetch,
CurrentIp(String),
}
struct App {
ip: String,
}
impl Application for App {
type Message = Message;
type Theme = iced::Theme;
type Flags = ();
type Executor = iced::executor::Default;
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
(App { ip: String::new() }, Command::none())
}
fn title(&self) -> String {
String::new()
}
fn view(&self) -> iced::Element<'_, Self::Message, Self::Theme, iced::Renderer> {
iced::widget::column![
iced::widget::text(&self.ip),
iced::widget::button("Start Command").on_press(Message::Refetch)
]
.into()
}
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
println!("update");
match message {
Message::Refetch => return Command::perform(fetch_ip(), Message::CurrentIp),
Message::CurrentIp(text) => {
self.ip = text;
}
}
Command::none()
}
}
async fn fetch_ip() -> String {
println!("fetch_ip");
reqwest::get("https://api.seeip.org")
.await
.unwrap()
.text()
.await
.unwrap()
}
fn main() {
App::run(iced::Settings::default()).expect("Application raised an error");
}
Note:
fetch_ip()
produces the future
Note:
Message::CurrentIp
is a shorthand for|x| Message::CurrentIp(x)
Full Code
use iced::{Application, Command};
#[derive(Debug, Clone)]
enum Message {
Refetch,
CurrentIp(String),
}
struct App {
ip: String,
}
impl Application for App {
type Message = Message;
type Theme = iced::Theme;
type Flags = ();
type Executor = iced::executor::Default;
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
(App { ip: String::new() }, Command::none())
}
fn title(&self) -> String {
String::new()
}
fn view(&self) -> iced::Element<'_, Self::Message, Self::Theme, iced::Renderer> {
iced::widget::column![
iced::widget::text(&self.ip),
iced::widget::button("Start Command").on_press(Message::Refetch)
]
.into()
}
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
println!("update");
match message {
Message::Refetch => return Command::perform(fetch_ip(), Message::CurrentIp),
Message::CurrentIp(text) => {
self.ip = text;
}
}
Command::none()
}
}
async fn fetch_ip() -> String {
println!("fetch_ip");
reqwest::get("https://api.seeip.org")
.await
.unwrap()
.text()
.await
.unwrap()
}
fn main() {
App::run(iced::Settings::default()).expect("Application raised an error");
}