Skip to main content

Overview

Presentum provides InheritedPresentumItem to pass presentation items down the widget tree without prop drilling. Outlets automatically wrap your builder widgets with InheritedPresentumItem, giving all descendant widgets access to the current item.

Automatic wrapping

When you create an outlet:
PresentumOutlet(
  surface: AppSurface.homeTopBanner,
  builder: (context, item) => MyCampaignWidget(),
)
Presentum automatically wraps it:
InheritedPresentumItem(
  item: item,
  child: MyCampaignWidget(),
)

Accessing the item

Descendants can access the item via context:
class CampaignActionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Get item from ancestor InheritedPresentumItem
    final item = context.presentumItem<
      CampaignItem,
      AppSurface,
      CampaignVariant
    >();

    return ElevatedButton(
      onPressed: () => context
          .presentum<CampaignItem, AppSurface, CampaignVariant>()
          .markConverted(item),
      child: Text(item.metadata['ctaText'] as String),
    );
  }
}
Use context.presentumItem<T, S, V>() to access the item. No need to pass it as a prop!

Benefits

No prop drilling

Before (manual passing):
PresentumOutlet(
  builder: (context, item) {
    return CampaignCard(
      item: item,  // Pass down
      child: CampaignContent(
        item: item,  // Pass down again
        child: CampaignButton(
          item: item,  // And again...
        ),
      ),
    );
  },
)
After (inherited):
PresentumOutlet(
  builder: (context, item) {
    return CampaignCard(
      child: CampaignContent(
        child: CampaignButton(), // No props needed!
      ),
    );
  },
)

class CampaignButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final item = context.presentumItem<CampaignItem, AppSurface, CampaignVariant>();
    // Use item...
  }
}

Cleaner component APIs

Components don’t need to know about Presentum items:
// Clean, reusable button
class ActionButton extends StatelessWidget {
  const ActionButton({this.label, super.key});

  final String? label;

  @override
  Widget build(BuildContext context) {
    final item = context.presentumItem<CampaignItem, AppSurface, CampaignVariant>();
    final text = label ?? item.metadata['ctaText'] as String;

    return ElevatedButton(
      onPressed: () => context
          .presentum<CampaignItem, AppSurface, CampaignVariant>()
          .markConverted(item),
      child: Text(text),
    );
  }
}

Custom observer widgets

If you build custom widgets that observe Presentum state (not using PresentumOutlet), you must wrap descendants manually:
class MyCustomObserver extends StatefulWidget {
  @override
  Widget build(BuildContext context) {
    final state = context.presentum<Item, Surface, Variant>().state;
    final item = state.slots[Surface.home]?.active;

    if (item == null) return const SizedBox.shrink();

    // ✅ Wrap so descendants can access item
    return InheritedPresentumItem(
      item: item,
      child: MyWidget(),
    );
  }
}
Forgetting to wrap? Child widgets will throw when trying to access the item:
InheritedPresentumItem.of<CampaignItem, AppSurface, CampaignVariant>() 
called with a context that does not contain a presentum item.
Fix: Wrap with InheritedPresentumItem or pass the item explicitly.

Alternative: Explicit props

If you prefer explicit props over inherited widgets, pass items manually:
// Instead of using inherited item
class CampaignButton extends StatelessWidget {
  const CampaignButton({required this.item, super.key});

  final CampaignItem item;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => context
          .presentum<CampaignItem, AppSurface, CampaignVariant>()
          .markConverted(item),
      child: Text(item.metadata['ctaText'] as String),
    );
  }
}

// Use in outlet
PresentumOutlet(
  builder: (context, item) {
    return CampaignButton(item: item); // Explicit prop
  },
)
Both approaches work. Choose based on your preference.

InheritedPresentum vs InheritedPresentumItem

Presentum provides two inherited widgets:
Provides the Presentum controller instance.
// Automatically provided by engine.build()
presentum.config.engine.build(context, child);

// Access anywhere
final presentum = context.presentum<Item, Surface, Variant>();

// Or
final presentum = Presentum.of<Item, Surface, Variant>(context);
Used for: Calling markShown, markDismissed, markConverted, etc.

Extension methods

Presentum provides convenient extension methods:
extension PresentumBuildContextX on BuildContext {
  // Get Presentum controller
  Presentum<TItem, S, V> presentum<TItem, S, V>() { /* ... */ }

  // Get current item (listen: false)
  TItem presentumItem<TItem, S, V>() { /* ... */ }

  // Get current item (listen: true)
  TItem watchPresentumItem<TItem, S, V>() { /* ... */ }
}
Use watchPresentumItem to rebuild when the item changes:
@override
Widget build(BuildContext context) {
  // Rebuilds when item changes
  final item = context.watchPresentumItem<CampaignItem, AppSurface, CampaignVariant>();

  return Text(item.metadata['title'] as String);
}

Next steps