Surface observers are widgets that watch a specific surface slot and react to active item changes. They provide a simple, reactive way to build custom UI that responds to Presentum state without the overhead of popup-specific features.The PresentumActiveSurfaceItemObserverMixin handles all the complexity of:
Subscribing to surface state - Automatically observes the correct slot
Tracking active item changes - Detects when items activate or deactivate
Managing lifecycle - Cleans up observers when widget disposes
Handling initial state - Optionally processes the initial active item
Use surface observers for snackbars, banners, overlays, animations, or any UI that needs to react to surface state changes without showing dialogs.
@overridePresentumSurface get surface; // Which surface to observe@overridevoid onActiveItemChanged({ required TItem? current, required TItem? previous,}); // React to changes
The onActiveItemChanged callback receives both current and previous items, enabling you to handle all state transitions:
Item activation (previous: null, current: Item)
A new item became active on an empty surface:
Copy
@overridevoid onActiveItemChanged({ required CampaignItem? current, required CampaignItem? previous,}) { if (current case final item? when previous == null) { // Show UI for new item showBanner(item); }}
Item deactivation (previous: Item, current: null)
The active item became inactive:
Copy
@overridevoid onActiveItemChanged({ required CampaignItem? current, required CampaignItem? previous,}) { if (previous case final item? when current == null) { // Hide UI for deactivated item hideBanner(); }}
@overridevoid onActiveItemChanged({ required CampaignItem? current, required CampaignItem? previous,}) { if (current case final newItem? when previous != null) { // Replace old item with new one updateBanner(from: previous, to: newItem); }}
Do not use surface observers for side effects or business logic that doesn’t involve UI rendering.If your observer’s build() method just returns widget.child with no UI changes, you’re using the wrong tool. Use Transition observers instead.
Examples of incorrect usage:
Triggering API calls when surface state changes
Starting/stopping timers based on state
Logging or analytics
BLoC/Provider integration
These are side effects and belong in transition observers, not widget-based observers.
@overridevoid onActiveItemChanged({ required Item? current, required Item? previous,}) { // Activation: null -> Item if (current case final item? when previous == null) { handleActivation(item); } // Deactivation: Item -> null if (previous case final item? when current == null) { handleDeactivation(item); } // Replacement: Item1 -> Item2 if (current case final newItem? when previous != null) { handleReplacement(from: previous, to: newItem); }}
Keep onActiveItemChanged lightweight
Avoid heavy computation in the callback. Use scheduleMicrotask or Future for async work:
Copy
@overridevoid onActiveItemChanged({ required Item? current, required Item? previous,}) { if (current == null) return; // ✅ Good - async work scheduled separately scheduleMicrotask(() => _performHeavyWork(current)); // ❌ Bad - blocks the callback // _performHeavyWork(current);}