Skip to main content

What is Presentum?

Presentum is a declarative Flutter engine for building dynamic, conditional UI at scale. It helps you manage campaigns, app updates, special offers, tips, notifications, and more with clean, testable, type-safe code. Instead of spreading show/hide logic across your widgets, you describe what should be shown as data, and Presentum’s engine, guards, and outlets handle where, when, and how it appears.
Think of Presentum as a presentation orchestrator that coordinates what users see based on rules, not imperative commands.

The problem

Most apps manage presentations by mixing logic across widgets and state managers:
// ❌ Logic spread everywhere, hard to test
class HomeScreen extends StatefulWidget {
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool _showBanner = false;
  Campaign? _campaign;

  @override
  void initState() {
    super.initState();
    _checkEligibility();
  }

  Future<void> _checkEligibility() async {
    final count = await prefs.getInt('banner_count') ?? 0;
    final lastShown = await prefs.getInt('banner_last_shown');

    if (count < 3 && (lastShown == null || /* complex check */)) {
      final campaign = await fetchCampaign();
      if (campaign != null && campaign.isActive && !userIsPremium) {
        setState(() {
          _showBanner = true;
          _campaign = campaign;
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (_campaign case final campaign? when _showBanner)
          BannerWidget(
            campaign: campaign,
            onClose: () => _handleDismiss(),
          ),
        // other content
      ],
    );
  }
}
Problems:
  • Business logic mixed with UI code
  • Hard to test eligibility rules
  • Difficult to coordinate multiple presentations
  • Impression tracking code everywhere
  • No single source of truth

The solution

Presentum separates what (payloads), when (guards), where (surfaces), and how (outlets):
// ✅ Declarative, testable, maintainable

// 1. Define domain data
class CampaignPayload extends PresentumPayload<AppSurface, CampaignVariant> {
  final String id;
  final int priority;
  final Map<String, Object?> metadata;
  final List<PresentumOption<AppSurface, CampaignVariant>> options;
}

// 2. Define logic in guards
class CampaignGuard extends PresentumGuard<CampaignItem, AppSurface> {
  @override
  FutureOr<PresentumState<CampaignItem, AppSurface>> call(
    storage, history, state, candidates, context,
  ) async {
    for (final candidate in candidates) {
      if (await isEligible(candidate, storage)) {
        state.setActive(candidate.surface, candidate);
      }
    }
    return state;
  }
}

// 3. Display widget with outlet
class HomeTopBannerOutlet extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return PresentumOutlet<CampaignItem, AppSurface>(
      surface: AppSurface.homeTopBanner,
      builder: (context, item) {
        return BannerWidget(
          campaign: item.payload,
          onClose: () => context
              .presentum<CampaignItem, AppSurface>()
              .markDismissed(item),
        );
      },
    );
  }
}
All eligibility logic is centralized. The outlet renders. The payload is data. Guards contain business rules. Everything is testable.

When to use Presentum

Perfect for

  • Marketing campaigns and banners
  • App update notifications (Shorebird, CodePush)
  • Special offers with discount codes
  • Onboarding tips and tutorials
  • Feature announcements
  • System maintenance alerts
  • User-targeted messaging
  • A/B testing content

Not designed for

  • Navigation/routing (use GoRouter, AutoRoute)
  • Layout management (use Flutter widgets)
  • General app state (use Riverpod, BLoC)
  • Real-time chat messages
  • Transient toasts/snackbars

What Presentum handles

Presentum can evaluate ANY condition you need:
  • User segments (premium, free, trial)
  • Geographic location (country, region, city)
  • App version (force update for old versions)
  • Device type (phone, tablet, platform)
  • OS type (iOS, Android, Web)
  • User behavior (purchase history, usage patterns)
  • Time-based rules (holidays, business hours, date ranges)
  • A/B test groups
  • Feature flags
  • Custom business logic
The engine is flexible and scalable - if you can write a rule for it, Presentum can handle it.

How it works

Presentum coordinates the flow between your data sources, eligibility rules, and UI:
1

Fetch candidates

Your app fetches presentations from Firebase Remote Config, APIs, or local sources.
2

Feed to engine

Convert to items and feed to Presentum using setCandidates or setCandidatesWithDiff.
3

Guards process

Engine runs all guards in sequence to determine eligibility, apply rules, and update state.
4

State committed

New state is committed and observers are notified.
5

Outlets rebuild

Outlets watching affected surfaces rebuild with new active items.
6

User interacts

User dismisses or converts, which records to storage and may trigger guard re-evaluation.

Key concepts

1

Surfaces

Where presentations appear. Named locations in your UI.
enum AppSurface with PresentumSurface {
  homeTopBanner,
  watchlistHeader,
  popup;
}
Learn more →
2

Payloads

What you want to show. Your domain objects with metadata.
class CampaignPayload extends PresentumPayload<S, V> {
  final String id;
  final int priority;
  final Map<String, Object?> metadata;
  final List<PresentumOption<S, V>> options;
}
Learn more →
3

Guards

When to show. Your business logic and eligibility rules.
class CampaignGuard extends PresentumGuard<Item, Surface> {
  // Decide what gets shown based on rules
}
Learn more →
4

Outlets

Rendering presentations. Just UI code, no business logic.
PresentumOutlet<Item, Surface>(
  surface: AppSurface.homeTopBanner,
  builder: (context, item) => MyWidget(item),
)
Learn more →

Production example

Here’s a real-world implementation from a production app:
enum CampaignSurface with PresentumSurface {
  popup,
  watchlistHeader,
  watchlistFooter,
  menuTile;
}

enum CampaignVariant with PresentumVisualVariant {
  fullscreenDialog,
  dialog,
  banner,
  inline;
}
See full production implementation →

Next steps