Questions about reusable components and app design pattern

I was looking into this tutorial Structuring Apps - Unofficial Iced Guide and the example mentioned in there (e.g. icebreaker/src/screen/conversation.rs at master · hecrj/icebreaker · GitHub).

  1. I think I don’t entirely understand what is purpose of actions? Except maybe to pass down a Command/Task? But why would I need actions when I already have messages by which I can recognize what to do?
  2. What is the added value of the state in the component struct? Is it just about readability?
  3. I have an issue using this pattern with following case:
    Imagine having an application struct containing pre-fetched data (I like to pre-load stuff to memory to speed things up). This session data contain e.g. my todo list. Then I have a component that needs to use these todos (or filter them and then use). How can I implement the components update() method to get data from MyApp.session_data?

Example:

pub struct MyApp {
   session_data: SessionData
}

impl Application for MyApp {
   ...

    fn init(..) -> .. {
        let session_data = SessionData::fetch();

        MyApp {
            session_data
        }
    }

   ...
}

and the component e.g.

struct TodoListPage;

impl TodoListPage {

    pub fn update(&self, message: Message) -> Action {
        ??? how do I get the list from session_data? Do I just include it in a Message? How would this look with the Actions mentioned above?
    }

}

Sorry if those are stupid questions, coming from different language and gui framework entirely so I don’t know these concepts.
Thank you!

This might help you Structuring Apps - Unofficial Iced Guide.

The idea of an Action is to specify what the parent application needs to handle. Of course you can do that just by messages, but it gets very unclear which messages should be handled by the parent application and which by the component.

Thanks, that is the link I have on the first line of my question :grinning:

So the Action is there for the parent? In the example it is actually used in the parent, but then is it still part of the component? I wonder what is the benefit of this approach as opposed to e.g. just sending (transformed) input directly into view (e.g. todo’s to display). It feels there is so many steps this way, but I am sure I am missing something.

Let’s say you have a larger application. You have a component that allows the input of very complex data. Let’s say a character customizer for a game.
The message enum for that view has like 30 different messages.
One for each slider that changes the character, one for submit, one for randomization etc.
The Action returned by the update function would only need two variants (None and Sumitted(CharacterDesign)).
Your application only needs to handle these two actions. The application is not responsible for the 27 sliders nor the randomize button.

That allows you to reuse that view in any iced application.
It is very clear what the parent application needs to handle, only Submit. If you work on your project 3 years from now, you won’t remember what of the 30 possible messages is relevant for the parent application, but you will remember with the action.
Even if you think of using the view in another application or restructuring the way your application’s inner beings work (changing database crates etc.) you won’t have to touch the code of the view.
That’s the advantage of this architecture.

I see, that is a good example. So basically whenever I adjust anything in that component only I don’t need to “leak” that to the parent or main, right? But I still have to have something in the parent, because e.g. the slider will have to invoke the parent message with the component message as a content of it right? But it will always be one and the same message, like Message::CharacterDesign(character_design_message), is that correct?

Would you happen to know how to solve the issue with the session data above? I tried, but I was always hitting some walls. E.g. I was using mutable self in the parent (main) update method, but I was also passing the session as immutable (thus using the self as immutable). Or is there any pattern that would be used in case you need to have data preloaded and globally available?

Thanks a lot :slight_smile:

Yeah. Thats how this structure is used.

For you session data problem I see two solutions. One would be passing it as parameter to the update function of the page or wrapping it in something like std::sync::Arc<tokio::sync::Mutex<SessionData>> and just clone it when you create the page.

Thank you I will check that.
I would like to avoid cloning it, it is probably very ineffective, right? (I mean copy takes time and it takes more memory as well).

If you wrap you data as such here std::sync::Arc<tokio::sync::Mutex>` it would not actually be cloned. Arc is a reference counting pointer.
In other words only the reference to your data is cloned not the data itself.

Aha, thank you. I am still fairly new to rust so these are still kinda hard for me to grasp. I already tried to use some, but always ended up removing them because there were some issues (from me not knowing it well enough).

So I would just have this as a field of MyApp struct and then I always clone the reference? Just wanna make sure. Or is there a way to have it globally available (statically)?
Thank you

This part of the rust book might give you some insights into how to use Arc and Mutex and what they do. Shared-State Concurrency - The Rust Programming Language

1 Like