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");
}