bcquality/microsoft/knowledge/ui/notification-recall-needs-known-id.md
Michael Dieringer 750466c7f3 Add UI knowledge: notification recall Id and Page Management document routing
Two ui articles with compiled good/bad samples:
- notification-recall-needs-known-id: a notification that the code also
  recalls needs a fixed Id (single instance; recall before re-send) or
  per-record tracking through codeunit "Notification Lifecycle Mgt."
  (SendNotification[WithAdditionalContext] / RecallNotificationsForRecord,
  HandleDelayedInsert semantics). Never-recalled notifications are exempt.
- list-page-document-routing-uses-page-management: open documents from a
  mixed-type list with PageManagement.PageRun(Rec) instead of a hand-written
  case "Document Type" / Page.Run; register new tables through
  OnConditionalCardPageIDNotFound. Single-type opens and tables Page
  Management does not route are exempt.

Wired into al-ui-review (entry gate now covers notification send/recall
outside pages, tokens, high-signal mappings) and registered both pairs in
the ui review-fixtures override.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:47:57 +02:00

5.8 KiB

bc-version domain keywords technologies countries application-area
all
ui
notification
recall
notification-id
createguid
notification-lifecycle-mgt
sendnotification
sendnotificationwithadditionalcontext
recallnotificationsforrecord
handledelayedinsert
notification-context
al
w1
all

A notification that must be recalled needs an Id the code can find again

Description

Notification.Recall() withdraws the notification whose Id it carries. When Id is left unassigned, Send() assigns one. A later Recall() on a new Notification variable has no way to name that Id, so a warning sent that way cannot be withdrawn when its condition clears. It stays until the user dismisses it or the page instance closes. Microsoft Learn's own Id/Recall example uses a predefined Id "so that the notification can be recalled". Recall() does not fail on a notification that was never sent or was already recalled, so code with a known Id can recall unconditionally.

Which Id is right depends on how many instances can be shown at once:

  • At most one at a time (one condition per page or task): a fixed GUID, returned from a procedure or assigned as a literal. Base App's Analysis View.ShowResetNeededNotification assigns a literal Id, calls Recall(), then sets the message and calls Send(). Learn does not document what Send() does when a notification with the same Id is already displayed, so recall first rather than relying on Send() to replace it.
  • One per record (for example one warning per document line): a single fixed Id cannot tell the records apart. Use codeunit 1511 "Notification Lifecycle Mgt.". SendNotification(Notification, RecId) assigns CreateGuid() when Id is null, sends, and stores the Id against the RecordId in the temporary table "Notification Context". RecallNotificationsForRecord(RecId, HandleDelayedInsert) recalls every tracked notification for that record. When one record can carry several independent warnings, pass a fixed GUID per reason to SendNotificationWithAdditionalContext and RecallNotificationsForRecordWithAdditionalContext. Item-Check Avail. does this: a CreateGuid() Id per notification, its fixed availability GUID as the additional context.

Best Practice

Assign a fixed Id to any single-instance notification that the same code path can also withdraw, and recall it with that Id before re-sending updated content. See sample: notification-recall-needs-known-id.good.al.

For per-record notifications, send and recall through "Notification Lifecycle Mgt." instead of calling Send()/Recall() directly. The codeunit is SingleInstance, so tracking lasts for the session. While a record does not exist yet, its notification is stored under the table's empty RecordId. Pass HandleDelayedInsert = true when recalling for a record that may not be inserted yet, and false when recalling after the record is deleted, as Base App's own delete subscribers do. Base App's "Notification Lifecycle Handler" (codeunit 1508) moves tracked notifications on insert and rename, and recalls them on delete, only for the Base App tables it subscribes to, such as Sales Line. For another table, call SetRecordID, UpdateRecordID, and RecallNotificationsForRecord from that table's own insert, rename, and delete paths.

Anti Pattern

Code that both sends and recalls a notification, for example Send() when a condition holds and Recall() in the else branch or when the condition clears, but never assigns Id, or assigns a fresh CreateGuid() and calls Send()/Recall() directly. The Recall() cannot reach the notification that was sent. A single fixed Id shared by notifications for several records, sent and recalled directly, is the per-record form of the same mistake. See sample: notification-recall-needs-known-id.bad.al.

Not this pattern: a one-off informational notification that the code never recalls, which Learn's own Sales Order example sends without an Id; and a notification sent through "Notification Lifecycle Mgt." without an Id, because the codeunit assigns and tracks one.

References