What are guards?
Guards are the brain of Presentum. They contain your business logic for deciding what gets shown, when, and in what order. Guards run every time candidates change or when external triggers fire. They inspect:- Current state and history
- Storage (impressions, dismissals, conversions)
- Candidates (potential presentations)
- Context (user data, app state)
Basic guard
Here’s a simple guard that sets the highest priority campaign as active:Guard parameters
PresentumStorage
required
Persistence layer for tracking impressions, dismissals, and conversions.
List<PresentumHistoryEntry>
required
Complete history of state changes. Useful for analyzing past decisions.
PresentumState$Mutable
required
Mutable state you can modify. Changes you make here will be committed after
all guards run.
List<TItem>
required
All potential presentations. These are the items guards evaluate for
eligibility.
Map<String, Object?>
required
Shared data between guards. Pass information from one guard to the next.
Production examples
Scheduling guard
This guard from a production app handles priority, sequencing, impression limits, and cooldowns:Remove ineligible guard
Removes items that are no longer eligible (e.g., expired campaigns). This guard is generic and can be reused in any presentum:Sync state with candidates guard
Keeps state synchronized with latest candidate data using diff algorithm. This guard is generic and can be reused in any presentumGuard execution order
Guards run in sequence, with each guard receiving the state mutated by previous guards:Order matters! Guards early in the chain prepare data for later guards.
Refresh triggers
Guards can subscribe to external changes and re-run automatically:lifecycle.notifyListeners() is called, the engine re-runs all guards with current state.
State mutation methods
Inside guards, you can mutate state freely:- Set active
- Queue management
- Remove items
- Cancel transition
Context sharing
Pass data between guards using thecontext parameter:
Production guard chain
Here’s how a production app structures guards:Guard patterns
Impression limiting
Cooldown management
User targeting
Sequencing logic
Best practices
Keep guards focused
Keep guards focused
Each guard should have one responsibility. Don’t create one giant guard that
does everything. Good:
ImpressionLimitGuard, CooldownGuard,
SegmentTargetingGuard Bad: MegaGuardThatDoesEverythingUse eligibility system
Use eligibility system
For complex conditions, use the built-in eligibility system instead of
manual if/else chains.Learn more about eligibility ->
Don't call notifyListeners in guards
Don't call notifyListeners in guards
This creates infinite loops. Guards run automatically when needed.
Common mistakes
Next steps
Implementing guards
Step-by-step guard building guide
Eligibility system
Use declarative eligibility rules
Storage
Understand the storage interface
Production examples
See real-world guards