Migration from v0.4 to v0.5

Version 0.5 brings many exciting changes but also a few major breaking changes. In this chapter, all changes should be covered to help you migrating your existing code quickly.

Components

Component unifies several traits that existed before. It entirely replaces AppUpdate, ComponentUpdate, AsyncComponentUpdate, MessageHandler, MicroModel, MicroWidgets, Components and Widgets. Components are now more similar to MicroComponent which means they are much more flexible and don't need a typed connection to their parent component. This makes using the same component with different parent components much easier. Instead of accessing the parent model, you define Init as a type that includes all information to initialize the component.

The life cycle has changed a bit, too.

  1. Initialization of the root widget. The reason behind this is to allow parent components to access the root widget before the rest of the component is initialized.
  2. Initialize the component itself and the widgets. This happens in one method now, which makes it easier especially for more difficult initializations.
  3. Regarding updates, there hasn't been a lot of changes. However you can now optionally update view and model at once using update_with_view.
  4. The shutdown method is called when the component is destroyed. Components don't have to live for the entire application lifespan anymore.

Senders and messages

Components have three kinds of messages now:

  1. Input is the regular Msg type from the Model trait in 0.4.
  2. Output is the message type, that can be used to forward information to other components automatically (or () if you don't care about forwarding). You will find more information about initializing components in the next section.
  3. CmdOutput is the output of commands. Commands are futures that are executed in the background. They fully replace async workers. The result of this future is the CmdOutput message handled in update_cmd, similar to the regular update function.

The send! macro is obsolete. Please use sender.input(msg) instead.

Initializing components

Components are initialized in the init method of their parent. You simply call MyComponentType::builder() to get a ComponentBuilder. Then you launch the component by calling builder.launch(init_params) to receive a Connector. From the connector you can decide to automatically forward messages to another component or just detach the connector to get a Controller. The controller is the type you now store in the model of the parent component instead of creating a separate components struct. There's no Components trait necessary.

For types that implement Component that don't have any widgets (such as implementers of the Worker trait), you can call detach_worker from a ComponentBuilder. This spawns the internal runtime on a separate thread and gives you a WorkerController.

Helper traits

SimpleComponent

SimpleComponent is a simpler variant of Component that helps with the implementation of Component. Particularly, it doesn't support commands.

Worker

Worker is an even simpler variant of SimpleComponent that helps with the implementation of Component. Particularly, it doesn't support widgets and allows running the components update loop on a different thread by using detach_worker. This is the replacement for the previously separated RelmWorker type.

Factories

Factories now work very similar to components. In fact, the new FactoryComponent trait that replaces FactoryPrototype is almost identical to the Component trait. Messages can now be optionally passed by using the forward_to_parent method.

FactoryVec was entirely removed in favor of FactoryVecDeque. Edits to factories are now similar to Mutex and require a guard. When the guard is dropped, all changes are rendered automatically, so no render method is required anymore.

The view macro

  • In general, a lot of internal macros were moved to be just attributes like watch! and track! now written as #[watch] and #[track(optional_condition)].
  • Multiple arguments now don't need args!() but just () whereas tuples need two parenthesis (()).
  • Wrapping widgets into Some is now also an attribute #[wrap(Some)].
  • Additional arguments are now passed with square brackets [] instead of parenthesis ().
  • Cloning variables for closures is always done with square brackets [] instead of parenthesis ().
Old New
view! {
    gtk::HeaderBar {
        set_title_widget = Some(&gtk::Box) {
            append: group = &gtk::ToggleButton {
                set_label: watch!(model.label),

                connect_toggled(sender) => move |btn| {
                    // ...
                },
            },
        }
    }
}
view! {
    gtk::HeaderBar {
        #[wrap(Some)]
        set_title_widget = &gtk::Box {
            append: group = &gtk::ToggleButton {
                #[watch]
                set_label: model.label,

                connect_toggled[sender] => move |btn| {
                    // ...
                },
            },
        }
    }
}

The widget macro

  • The macro in now called #[component].
  • You need to use let widgets = view_output!(); in the init function to inject the code from the view macro.
  • pre_init -> Code before view_output!().
  • post_init -> Code after view_output!().

RelmApp

Now you need to specify an application id and a generic parameter is required when calling run().

Miscellaneous

  • WidgetPlus::inline_css now takes &str as parameter instead of &[u8].

Summary

v0.4v0.5
ModelComponent
AppUpdateComponent
ComponentUpdateComponent
AsyncComponentUpdateComponent
MicroComponentComponent
MicroWidgetsComponent
MessageHandlerComponent
FactoryPrototypeFactoryComponent
Model::MsgComponent::Input
Model::WidgetsComponent::Widgets
Model::Componentsremoved
ComponentsStore a Controller for each component in your model
parent_senderForward output messages from one component to another by using Connector

In case there's something missing, please let me know. You can simply open an issue on GitHub or write a message in the Matrix room.