Overview
Presentum uses three related types to represent presentations:
Payloads - Your domain data (campaigns, tips, updates)
Options - How payloads appear (surface + variant + rules)
Items - Concrete decisions (payload + option)
Payloads
Payloads are your domain objects containing all presentation data:
class CampaignPayload extends PresentumPayload < AppSurface , CampaignVariant > {
const CampaignPayload ({
required this .id,
required this .priority,
required this .metadata,
required this .options,
});
@override
final String id;
@override
final int priority;
@override
final Map < String , Object ?> metadata;
@override
final List < PresentumOption < AppSurface , CampaignVariant >> options;
}
Required fields
Unique identifier for the payload. Used for tracking and deduplication.
Display priority. Higher priority items shown first. Typical range: 0-1000.
metadata
Map<String, Object?>
required
Arbitrary domain data. Store titles, images, URLs, eligibility data, etc.
options
List<PresentumOption>
required
How this payload can be presented across different surfaces.
Production example
From a real app with JSON deserialization:
class CampaignPayload extends PresentumPayload < CampaignSurface , CampaignVariant >
implements HasMetadata {
const CampaignPayload ({
required this .id,
required this .priority,
required this .metadata,
required this .options,
});
factory CampaignPayload . fromJson ( Map < String , Object ?> json) {
return CampaignPayload (
id : json[ 'id' ] as String ,
priority : (json[ 'priority' ] as num ? ) ? . toInt () ?? 0 ,
metadata : json[ 'metadata' ] as Map < String , Object ?>,
options : (json[ 'options' ] as List )
. map ((o) => CampaignPresentumOption . fromJson (o))
. toList (),
);
}
@override
final String id;
@override
final int priority;
@override
final Map < String , Object ?> metadata;
@override
final List < CampaignPresentumOption > options;
Map < String , Object ?> toJson () => {
'id' : id,
'priority' : priority,
'metadata' : metadata,
'options' : options. map ((o) => o. toJson ()). toList (),
};
}
See full payload implementation ->
Options
Options describe how a payload appears on a specific surface:
class CampaignPresentumOption
extends PresentumOption < AppSurface , CampaignVariant > {
const CampaignPresentumOption ({
required this .surface,
required this .variant,
required this .isDismissible,
this .stage,
this .maxImpressions,
this .cooldownMinutes,
this .alwaysOnIfEligible = false ,
});
@override
final AppSurface surface;
@override
final CampaignVariant variant;
@override
final bool isDismissible;
@override
final int ? stage;
@override
final int ? maxImpressions;
@override
final int ? cooldownMinutes;
@override
final bool alwaysOnIfEligible;
}
Option fields
surface
S extends PresentumSurface
required
Where this option appears (e.g., AppSurface.homeTopBanner)
variant
V extends PresentumVisualVariant
required
How this option is displayed (e.g., CampaignVariant.banner)
Whether users can close this presentation
Sequence hint for multi-stage flows (e.g., 0 = fullscreen, 1 = dialog)
Maximum times to show. null = unlimited
Minutes to wait between shows. null = no cooldown
Show immediately when eligible without requiring explicit activation
Multi-surface payloads
One payload can have multiple options for different surfaces:
CampaignPayload (
id : 'black-friday-2025' ,
priority : 100 ,
metadata : {
'title' : 'Black Friday Sale' ,
'discount' : '50%' ,
},
options : [
// Popup option
CampaignPresentumOption (
surface : AppSurface .popup,
variant : CampaignVariant .fullscreenDialog,
maxImpressions : 1 ,
cooldownMinutes : null ,
isDismissible : true ,
),
// Banner option
CampaignPresentumOption (
surface : AppSurface .homeTopBanner,
variant : CampaignVariant .banner,
maxImpressions : null ,
cooldownMinutes : 1440 , // 24 hours
isDismissible : true ,
alwaysOnIfEligible : true ,
),
],
)
This lets the same campaign appear as both a popup and a banner, with different rules for each.
Items
Items combine a payload with a specific option:
class CampaignPresentumItem
extends PresentumItem < CampaignPayload , AppSurface , CampaignVariant > {
const CampaignPresentumItem ({
required this .payload,
required this .option,
});
@override
final CampaignPayload payload;
@override
final CampaignPresentumOption option;
}
Items provide derived properties:
final item = CampaignPresentumItem (
payload : campaign,
option : campaign.options.first,
);
item.id // "black-friday-2025::fullscreenDialog::popup"
item.surface // AppSurface.popup
item.variant // CampaignVariant.fullscreenDialog
item.priority // 100 (from payload)
item.metadata // {'title': 'Black Friday Sale', ...}
item.stage // 0 (from option)
Creating items
Convert payloads to items when feeding to engine:
final payload = CampaignPayload ( /* ... */ );
// Create items from all options
final items = payload.options. map ((option) {
return CampaignPresentumItem (
payload : payload,
option : option,
);
}). toList ();
// Feed to engine
engine. setCandidatesWithDiff ((state) => items);
Type aliases
Use type aliases for cleaner code:
// Define once
typedef CampaignItem = CampaignPresentumItem ;
typedef CampaignGuard = PresentumGuard < CampaignItem , CampaignSurface , CampaignVariant >;
typedef CampaignState = PresentumState < CampaignItem , CampaignSurface , CampaignVariant >;
// Use everywhere
class MyGuard extends CampaignGuard {
@override
FutureOr < CampaignState > call (...) async {
// Much cleaner!
}
}
See production typedefs ->
metadata : {
'title' : 'Black Friday Sale' ,
'message' : '50% off premium features' ,
'imageUrl' : 'https://...' ,
'ctaText' : 'Shop Now' ,
'ctaUrl' : '/shop/black-friday' ,
'backgroundColor' : '#1E293B' ,
'time_range' : {
'start' : '2025-11-29T00:00:00Z' ,
'end' : '2025-12-02T23:59:59Z' ,
},
'required_segments' : [ 'active_users' ],
'is_active' : true ,
}
metadata : {
'version' : '2.0.0' ,
'buildNumber' : 42 ,
'isForced' : false ,
'releaseNotes' : 'New features and improvements' ,
'downloadUrl' : 'https://...' ,
'minRequiredVersion' : '1.5.0' ,
}
metadata : {
'title' : 'Swipe to refresh' ,
'description' : 'Pull down to see latest updates' ,
'iconName' : 'refresh' ,
'helpUrl' : '/help/refresh' ,
'requiredCompletedSteps' : [ 'signup' , 'onboarding' ],
}
Best practices
Keep metadata flat when possible
Establish priority ranges for different types:
0-99: Low priority tips
100-199: Regular campaigns
200-299: Important updates
300+: Critical alerts (maintenance, force updates)
Add fromJson/toJson for Remote Config integration: factory CampaignPayload . fromJson ( Map < String , Object ?> json) {
// Deserialize
}
Map < String , Object ?> toJson () {
// Serialize
}
Next steps