Skip to content
Haiku OSDeep Dive Published Updated 7 min readViews unavailable

Haiku BAlert: Modal Results, Asynchronous Replies, and Safe Teardown

Use Haiku BAlert for concise decisions with explicit button semantics, checked modal results, asynchronous invokers, and predictable window teardown.

BAlert is a small modal dialog for a short message and a set of labeled responses. Its API looks simple, but the important production decisions are not about drawing the alert: they are about whether the caller blocks, what each button means, how the result is validated, and who owns the alert’s lifetime. A well-designed alert is a bounded interruption that helps a user make a decision. It is not a substitute for inline validation, a progress window, a settings panel, or a way to surface every recoverable condition.

The Haiku Book documents two distinct Go() entry points. The no-argument form displays the alert and synchronously returns an integer button result. The Go(BInvoker*) form displays the alert using an invoker for asynchronous delivery and returns a status_t for starting that operation. Do not confuse the asynchronous method’s return value with the user’s choice: the selected button is placed in the invoked message under the which field. Code that handles these two paths as though they shared one return contract can silently treat “shown successfully” as “user accepted.”

Give buttons stable meaning

The constructor accepts a title, message, and up to three button labels. Choose labels that describe the action, such as “Keep Editing” and “Discard,” rather than generic labels that force the user to reread the message. Put the least destructive or most common safe choice in the appropriate position for the workflow, and make destructive consequences explicit in the text. A button index is an implementation detail; never infer meaning from a translated button label or assume a particular index without checking the constructor order used by that alert.

#include <Alert.h>
#include <SupportDefs.h>

enum SaveDecision {
	kSaveChanges,
	kDiscardChanges,
	kKeepEditing
};

SaveDecision AskAboutUnsavedChanges()
{
	BAlert* alert = new BAlert("Unsaved changes",
		"The document has edits that have not been saved.",
		"Save", "Discard", "Keep Editing",
		B_WIDTH_AS_USUAL, B_WARNING_ALERT);

	const int32 choice = alert->Go();
	switch (choice) {
		case 0: return kSaveChanges;
		case 1: return kDiscardChanges;
		case 2: return kKeepEditing;
		default:
			// Treat an unexpected result as the non-destructive choice.
			return kKeepEditing;
	}
}

This is a synchronous example for a caller that can wait for the modal choice. The sample maps the integer result into an application-level enum immediately, so the rest of the program does not depend on button positions. Production code should also handle alert construction or display failure if its surrounding API contract exposes those conditions. Never continue a destructive operation on an unrecognized value; define a conservative fallback.

Keep the text short enough to scan. If the user needs to compare several complex alternatives, explain consequences in the owning view and let the dialog confirm one choice. If the operation can take a while, the alert should ask whether to begin or cancel; a progress UI should report ongoing work without keeping a modal prompt open. Repeated alerts for routine conditions train users to dismiss them without reading, so use inline states for errors that can be corrected in place.

Understand synchronous Go()

The no-argument Go() returns an int32 identifying the selected button. It presents a modal interaction and does not return until the alert is dismissed. This can make simple command handlers easy to reason about: ask, validate the result, then perform the selected action. It can also make a poor architecture if used from a worker that must continue processing, from a code path holding a lock, or from a transaction that should not remain open while a person decides.

Do not hold an application-wide lock across a user interaction. The user may take minutes to respond, close another window, or trigger work whose completion needs the same lock. Gather the state needed to compose the prompt, release locks, show the alert, then revalidate the state before committing the chosen action. A decision about a document that has changed while the prompt was open may no longer be valid.

Modal behavior is also a workflow choice. It prevents the user from proceeding through the relevant modal relationship until a response arrives, but it does not guarantee the operation behind the alert remains valid. Treat the dialog as a request for intent, not as a lease on files, network connections, or model state. Confirm prerequisites again when the result is applied.

Use the invoker overload for asynchronous choice

An asynchronous alert is useful when the caller must remain responsive while the user decides. Go(BInvoker*) accepts an invoker and returns a status code; the selected button is delivered in the invoker’s message as the integer field which. Set a distinct command code for the message and validate both the message code and field before using the result. The receiver should translate that button index into a semantic decision just as the synchronous path does.

The invoker and its target need a valid lifetime for the entire interaction. In a window-based application, a BInvoker can target a handler/looper through the application’s normal message routing. Store it in an object whose lifetime covers the alert, or use another ownership arrangement whose contract is explicit. Avoid a stack-local target or invoker if it will be destroyed as soon as Go() returns. A message arriving after the owning view begins teardown must be rejected or safely ignored rather than dereferencing stale state.

The alert is a BWindow subclass. The asynchronous path is not equivalent to “return a future that owns the alert.” Respect the window and alert teardown behavior documented by Haiku; after the user responds and the alert closes, do not keep using a raw alert pointer unless your code has a separately documented lifetime guarantee. If cancellation, parent-window closure, or application shutdown can occur during the prompt, make that part of the receiver’s state machine. A weak model or explicit generation token can prevent a late decision from applying to a newer document session.

Keep the action idempotent and revalidated

An alert can be closed or its result can arrive after the user’s context changed. For example, a prompt to replace a file may become stale if another process replaces that file while the alert is open. The response handler should resolve the current object again and check its identity or version before acting. If the original operation is no longer possible, present a current explanation in the owning view rather than silently applying a different operation.

Design each action so duplicate delivery cannot corrupt state. A button should not be able to start the same destructive task twice because the receiver processes a repeated command. Mark the decision as consumed, use an operation identifier, or make the underlying mutation idempotent. This is especially important when an action dispatches work to another thread or service: the UI acknowledgment and the underlying result are separate events.

For destructive actions, consider whether a confirmation alert is actually the right safeguard. A recoverable trash operation, versioned save, undo record, or explicit preview may protect users better than an extra “Are you sure?” prompt. If an alert is necessary, name the object and consequence precisely, provide a safe escape, and ensure the default keyboard action does not accidentally select the destructive option.

Test responses and shutdown paths

Test every button, keyboard navigation, default button, window close, and the asynchronous message path. Verify that each button maps to the intended semantic enum, that unexpected results do not execute a destructive action, and that the receiver handles a missing or malformed which field. Also test a parent window closing while the alert is visible, a target handler being detached, and the underlying document changing before the response arrives.

For synchronous calls, test that the caller does not hold a lock or block a critical service while waiting. For asynchronous calls, verify the invoker and target remain valid until the result is routed, and that shutdown invalidates pending work safely. Record the actual status_t returned by the asynchronous Go() call separately from the later button choice. A successful request to show an alert does not mean that the user approved it.

Use alerts sparingly, map indices at the UI boundary, and make the receiver own the final validation. That yields a dialog whose behavior remains understandable under localization, user delay, concurrent changes, and application shutdown.

Related:

Sources:

Comments