Custom Task 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 task.

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.13.1", features = ["tokio"]}
reqwest = "0.11.24"

Making the api request

At first, we define what our task 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::Task;

#[derive(Debug, Clone)]
enum Message {
    Refetch,
    CurrentIp(String),
}

struct App {
    ip: String,
}

impl App {
    fn new() -> (Self, iced::Task<Message>) {
        (App { ip: String::new() }, Task::none())
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            iced::widget::text(&self.ip),
            iced::widget::button("Start task").on_press(Message::Refetch)
        ]
        .into()
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        println!("update");
        match message {
            Message::Refetch => return Task::perform(fetch_ip(), Message::CurrentIp),
            Message::CurrentIp(text) => {
                self.ip = text;
            }
        }
        Task::none()
    }
}

async fn fetch_ip() -> String {
    println!("fetch_ip");
    reqwest::get("https://api.seeip.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
}

fn main() {
    iced::run("Custom Task Example", App::update, App::view).unwrap();
}

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 task or subscription to run a closure on a thread where blocking is acceptable.

Starting/Creating the task

In the update function we return Task::none() or our custom task depending on the message.

If the Message is Message::CurrentIp we change our state, if it is Message::Refetch we return our task.

use iced::Task;

#[derive(Debug, Clone)]
enum Message {
    Refetch,
    CurrentIp(String),
}

struct App {
    ip: String,
}

impl App {
    fn new() -> (Self, iced::Task<Message>) {
        (App { ip: String::new() }, Task::none())
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            iced::widget::text(&self.ip),
            iced::widget::button("Start task").on_press(Message::Refetch)
        ]
        .into()
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        println!("update");
        match message {
            Message::Refetch => return Task::perform(fetch_ip(), Message::CurrentIp),
            Message::CurrentIp(text) => {
                self.ip = text;
            }
        }
        Task::none()
    }
}

async fn fetch_ip() -> String {
    println!("fetch_ip");
    reqwest::get("https://api.seeip.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
}

fn main() {
    iced::run("Custom Task Example", App::update, App::view).unwrap();
}

To create our custom task, we use the Task::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::Task;

#[derive(Debug, Clone)]
enum Message {
    Refetch,
    CurrentIp(String),
}

struct App {
    ip: String,
}

impl App {
    fn new() -> (Self, iced::Task<Message>) {
        (App { ip: String::new() }, Task::none())
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            iced::widget::text(&self.ip),
            iced::widget::button("Start task").on_press(Message::Refetch)
        ]
        .into()
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        println!("update");
        match message {
            Message::Refetch => return Task::perform(fetch_ip(), Message::CurrentIp),
            Message::CurrentIp(text) => {
                self.ip = text;
            }
        }
        Task::none()
    }
}

async fn fetch_ip() -> String {
    println!("fetch_ip");
    reqwest::get("https://api.seeip.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
}

fn main() {
    iced::run("Custom Task Example", App::update, App::view).unwrap();
}

Note: fetch_ip() produces the future

Note: Message::CurrentIp is a shorthand for |x| Message::CurrentIp(x)

Full Code

use iced::Task;

#[derive(Debug, Clone)]
enum Message {
    Refetch,
    CurrentIp(String),
}

struct App {
    ip: String,
}

impl App {
    fn new() -> (Self, iced::Task<Message>) {
        (App { ip: String::new() }, Task::none())
    }

    fn view(&self) -> iced::Element<Message> {
        iced::widget::column![
            iced::widget::text(&self.ip),
            iced::widget::button("Start task").on_press(Message::Refetch)
        ]
        .into()
    }

    fn update(&mut self, message: Message) -> iced::Task<Message> {
        println!("update");
        match message {
            Message::Refetch => return Task::perform(fetch_ip(), Message::CurrentIp),
            Message::CurrentIp(text) => {
                self.ip = text;
            }
        }
        Task::none()
    }
}

async fn fetch_ip() -> String {
    println!("fetch_ip");
    reqwest::get("https://api.seeip.org")
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
}

fn main() {
    iced::run("Custom Task Example", App::update, App::view).unwrap();
}