03 - MVU and messages¶
FlowUI uses a small Model-View-Update loop:
| Role | Signature | Responsibility |
|---|---|---|
| Model | Any application value | Business state |
| Msg | A closed set of message types | What happened |
| Update | func(*M, Msg) Cmd[Msg] |
Apply state changes and effects |
| View | func(*Context, M, Send[Msg]) Widget |
Render the current state |
Message dispatch¶
Use a Go type switch for a small Update. When the number of message handlers
grows, ui.Match lets you split the cases into typed handlers. Both styles are
supported; choose based on the size of the update function.
func Update(m *Model, msg Msg) ui.Cmd[Msg] {
return ui.Match(m, msg,
ui.On(func(m *Model, _ Increment) ui.Cmd[Msg] {
m.Count++
return nil
}),
ui.On(func(m *Model, msg NameChanged) ui.Cmd[Msg] {
m.Name = msg.Name
return nil
}),
)
}
ui.On infers the model, message, and command types from each handler. ui.Match
runs the first matching handler and returns nil when none match. Handlers can
live in domain-specific functions and be composed, while the application still
owns its message types. A type switch remains straightforward when seeing every
case in one place is more useful.
Closed message sets¶
A private marker method keeps the message set explicit and makes type switches easy to review:
type Msg interface{ msg() }
type NameChanged struct{ Value string }
type Submitted struct{}
func (NameChanged) msg() {}
func (Submitted) msg() {}
Update handles messages and returns an optional command:
func Update(m *Model, msg Msg) ui.Cmd[Msg] {
switch msg := msg.(type) {
case NameChanged:
m.Name = msg.Value
case Submitted:
return submitCmd(m.Name)
}
return nil
}
Single-window programs¶
Use NewProgram when the initial model is a fixed value:
Use a full Program value when initialization returns a command or must create
fresh reference-backed state:
ui.Run(ui.Program[Model, Msg]{
Init: func() (Model, ui.Cmd[Msg]) {
return Model{}, loadInitial()
},
Update: Update,
Subscriptions: func(Model) []ui.Subscription[Msg] { return nil },
View: View,
})
Nested modules¶
Child modules can keep their own model and messages. The parent wraps child
messages with ui.MapCmd and maps the child's Send callback into a parent
message. A complete runnable pattern is maintained in the separate examples
module.
Actions and commands¶
Cmd represents asynchronous effects such as network requests, file IO, and
timers. Action and Command describe reusable menu, toolbar, and shortcut
actions. They are related but serve different purposes.
Continue with Commands and subscriptions.