App lifecycle (§ lifecycle)

A Day app can hook the moments its process moves through: launching, gaining and losing focus, going to and from the background, running low on memory, and terminating. Each phase maps to the host OS’s native app/activity delegate, so a handler runs at the right native moment on every platform.

use day::prelude::*;

fn main() {
    // Register before launch so `WillLaunch` / `DidLaunch` are captured.
    day::on_lifecycle(Lifecycle::DidLaunch,    || println!("up and running"));
    day::on_lifecycle(Lifecycle::WillTerminate, || save_state());   // ⌘Q / Ctrl+Q / OS quit

    day::launch(WindowOptions::default(), app::root);
}

Handlers run in registration order, inside a reactive batch, so a lifecycle handler that writes a Signal updates the UI just like a button callback. Register as many as you like per phase.

The phases

PhaseWhen
WillLaunchBefore the window/UI exists; the first thing to run. Set up global state.
DidLaunchThe UI is mounted and laid out; the app is about to run.
DidBecomeActiveCame to the foreground and is the focused, interactive app.
WillResignActiveAbout to stop being active (an interruption, app switch, losing focus).
WillEnterForeground(mobile) About to return to the foreground.
DidEnterBackground(mobile) Left the foreground and is no longer visible. Persist state.
DidReceiveMemoryWarning(mobile) The system is low on memory. Drop caches.
WillTerminateAbout to terminate; the last chance to save.

WillLaunch and DidLaunch are emitted uniformly by day-core (reliable everywhere); the rest come from each backend’s native app/activity delegate.

When to register

Register before day::launch (in main) to catch WillLaunch/DidLaunch. Registering later (inside your root builder, say) still catches everything from DidLaunch onward. On the mobile shells, the root builder is the natural registration point (there is no main you own); those handlers see DidLaunch and after.

Platform availability

Not every phase exists on every platform: a desktop app doesn’t enter the background or run out of memory the way a phone does. The universal phases (WillLaunch, DidLaunch, DidBecomeActive, WillResignActive, WillTerminate) are delivered by every backend. The background/foreground/memory phases are delivered only by the mobile backends (UIKit, Android).

PhaseAppKitGTKQtUIKitAndroidXAML
WillLaunch / DidLaunch
DidBecomeActive / WillResignActive
WillEnterForeground / DidEnterBackground
DidReceiveMemoryWarning
WillTerminate

Native mapping: AppKit NSApplication notifications; UIKit UIApplicationDelegate; GTK window is-active + GApplication shutdown; Qt applicationStateChanged + aboutToQuit; Android Activity lifecycle (onResume/onPause/onStart/onStop/onTrimMemory/onDestroy); XAML window WM_ACTIVATE/WM_CLOSE.

Guarding platform-specific phases

Registering a handler for a phase the running backend doesn’t deliver is not an error: the handler is never called, and Day logs a one-time warning at launch:

day: an `on_lifecycle(DidEnterBackground)` handler was registered, but this backend never delivers
that phase, so it will not run. Guard it with `day::lifecycle_supported(..)` or a
`day::require_lifecycle!(..)` compile-time check.

There are two ways to guard, depending on how strict you want to be.

Soft: register only where supported. day::lifecycle::supported is a const fn that knows the backend compiled into this binary, so it’s false on desktop and true on mobile for the mobile-only phases:

if day::lifecycle::supported(Lifecycle::DidEnterBackground) {
    day::on_lifecycle(Lifecycle::DidEnterBackground, || flush_to_disk());
}

(There is also a runtime day::lifecycle_supported(phase) for checks after the app is up.)

Hard: require it at compile time. If your app depends on a phase, assert it and get a build error on a backend that can’t deliver it:

day::require_lifecycle!(Lifecycle::DidEnterBackground);  // compile error on a desktop backend

Quit

WillTerminate fires on every quit path: the Quit menu command (menu_role(MenuRole::Quit)), the platform quit shortcut (⌘Q on macOS, Ctrl+Q elsewhere), and the OS reclaiming the app. The Quit command exits the app. On GTK a standard app.quit action is registered so ⌘Q / Ctrl+Q and the menu item both work; on macOS the App-menu Quit is standard; Qt/XAML quit their event loops. Save work in a WillTerminate handler.

Mobile note: iOS/Android apps don’t have a user-facing “quit”. There, WillTerminate means the OS is tearing the app down (Android onDestroy while finishing; iOS applicationWillTerminate:). Prefer DidEnterBackground for “the user left” on mobile.

How it works

on_lifecycle records the closure in a day-core registry keyed by phase. Backends emit Event::Lifecycle(phase) from their native delegate (the same event rail as menu actions); the event pump routes it to dispatch_lifecycle, which runs the phase’s handlers in a reactive batch. Adding lifecycle support to a new backend is two things: implement Toolkit::supports_lifecycle (and the matching const fn lifecycle_supported), and emit Event::Lifecycle(..) at the right native moments.