Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungAutomating Bug Reports with Tally and Make(22.09.2026 um 22:44 Uhr)
Sichere ProgrammierungHTB - Forest(22.09.2026 um 22:49 Uhr)
Sichere ProgrammierungYour Ports Are Lying About Your Business(22.09.2026 um 22:52 Uhr)
Sichere ProgrammierungOLTP vs. OLAP: Understanding the Foundation of Modern Data Systems(22.09.2026 um 22:54 Uhr)
Sichere ProgrammierungYour AI Meeting Assistant Is Taking Notes. Who Is Doing the Work?(22.09.2026 um 22:57 Uhr)
Sichere ProgrammierungDebuggear se va a acabar(22.09.2026 um 23:03 Uhr)
Sichere ProgrammierungAutomating Bug Reports with Tally and Make(22.09.2026 um 22:44 Uhr)
Sichere ProgrammierungHTB - Forest(22.09.2026 um 22:49 Uhr)
Sichere ProgrammierungYour Ports Are Lying About Your Business(22.09.2026 um 22:52 Uhr)
Sichere ProgrammierungOLTP vs. OLAP: Understanding the Foundation of Modern Data Systems(22.09.2026 um 22:54 Uhr)
Sichere ProgrammierungYour AI Meeting Assistant Is Taking Notes. Who Is Doing the Work?(22.09.2026 um 22:57 Uhr)
Sichere ProgrammierungDebuggear se va a acabar(22.09.2026 um 23:03 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Our Home Screen Renders 12 Components — Without Knowing Any of Them

The Widget Factory pattern that lets our CMS control what 2M+ users see — without a single app release. Our home screen serves millions of page views per month. It renders carousels, banners, sign-in cards, feeds, grids, and more. The …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

The Widget Factory pattern that lets our CMS control what 2M+ users see — without a single app release.






Our home screen serves millions of page views per month. It renders carousels, banners, sign-in cards, feeds, grids, and more.



The screen has no idea what it's rendering.



A content team in a CMS dashboard decides what appears, in what order, for which users — and the app renders it. No code change. No app release. No PR review. Here's the architecture that makes that possible.









The Problem: Every Home Screen Change Required a Release



Twelve months ago, our home screen was a Column with hardcoded widgets. Product carousel at the top, banner in the middle, categories at the bottom. Always. For everyone.



It wasn't completely static — data came from the CMS — but the layout was frozen in code. The feed widget alone was 350+ lines of nested if blocks:




if (banners != null && shouldShowBannerCarousel) ...
if (shouldShowMosaicGrid) ...
if (isGuestUser && shouldShowSecondaryMosaic) ...
if (ugcPosition == FeedPosition.top) ...
if (categories != null) ...
// and on and on






There was even a TODO buried in the file: "Create a method that returns home feed widgets dynamically." We'd been telling ourselves to fix it for months.



Want to swap the banner and carousel? Code change. Want to show a sign-in card only to guest users? Another if block. Want to A/B test a new component position? Two code changes and a feature flag.



The content team had ideas faster than we could ship them. We were the bottleneck — and every "quick layout tweak" took a sprint.



My colleague Dip and I got the assignment: make the home screen data-driven. The CMS should define what appears and where. The app should just render it.









The Architecture: Four Layers, One Factory



Here's the data flow Dip and I designed, from CMS to screen:



Flow diagram showing CMS-driven UI rendering: Contentful CMS → GraphQL API → DTO layer → domain entities → BLoC → ListView → widget factory → dynamically rendered UI components.



The critical insight: the ListView doesn't care what types exist. It iterates a list and delegates every item to a WidgetFactory. The factory resolves "ProductCarousel"CarouselWidgetBuilderCarouselWidget. The screen never imports a single component.



Think of it as: CMS sends JSON → app turns it into widgets. Everything in between is just type safety and caching.









The Widget Factory: O(1) Type Resolution



The factory is a singleton with a pre-built lookup map. On init, it iterates an enum of supported types and caches each builder:




class WidgetFactory {
static final WidgetFactory _instance = WidgetFactory._internal();
static WidgetFactory get instance => _instance;

late final Map<String, IWidgetBuilder> _builderCache;

WidgetFactory._internal() {
_builderCache = {
for (final model in SupportedComponentType.values)
model.cmsId: model.widgetBuilder,
};
}

Widget createWidget(BuildContext context, LayoutWidgetEntity item) {
final builder = _builderCache[item.contentModelType];
if (builder == null || !builder.canHandle(item)) {
return const SizedBox.shrink(); // Unknown type? Skip silently.
}
return builder.build(context, item);
}
}






No switch statement. No if-else chain. A Map lookup — O(1) regardless of how many component types we add. Unrecognized types from the CMS render as empty space, not crashes.



Flow of widget rendering









The Registry: One Enum to Rule Them All



Every supported component type lives in a single enum. Each entry maps a CMS string to a concrete builder:




enum SupportedComponentType {
productCarousel,
signInCard,
contentBanner,
userContent,
contextualMessage,
infoBanner,
recentActivity,
mosaicGrid,
promotionBanner,
categoryCarousel,
// ...12 types total

String get cmsId => switch (this) {
productCarousel => 'ProductCarousel',
signInCard => 'SignInCard',
contentBanner => 'ContentBanner',
// ...
};

IWidgetBuilder get widgetBuilder => switch (this) {
productCarousel => CarouselWidgetBuilder(),
signInCard => SignInCardWidgetBuilder(),
// ...
};
}






Adding a new component type = one enum entry + one builder class. The factory, the screen, the cache, the BLoC — none of them change. Dip and I have watched teammates add a new component in under two hours. Most of that time was writing the widget itself.









Each Component is an Island



Every builder wraps its widget in its own BlocProvider. The product carousel gets a CarouselBloc. The announcement banner gets an AnnouncementBloc. They share nothing.




class CarouselWidgetBuilder implements IWidgetBuilder {
@override
bool canHandle(LayoutWidgetEntity layout) {
return layout.contentModelType == 'ProductCarousel'
&& layout.sysId.isNotEmpty;
}

@override
Widget build(BuildContext context, LayoutWidgetEntity entity) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => getIt<CarouselBloc>()),
BlocProvider(create: (_) => getIt<CarouselItemBloc>()),
],
child: CarouselWidget(widgetEntity: entity),
);
}
}






This isolation didn't happen on day one. We initially tried sharing BLoCs between components — it broke within a week. One component's loading state leaked into another's UI. That's when Dip and I enforced full isolation: if you're a component, you bring your own state management.









Polymorphic Data: Same Widget, Seven Sources



Here's the part that surprised both of us. The same ProductCarousel component renders data from seven different backends — same UI, different data sources selected via CMS, zero code changes:








































Data Source What It Shows
CMS Editorially curated products
PersonalizationBFF Personalized for you
NewArrivalsBFF New products
RepurchaseBFF Previously purchased
TrendingBFF Trending items
ComplementaryBFF "Goes well with..."
CategoryBFF Category-based suggestions


The CarouselBloc reads entity.dataSourceType and calls the right repository. The widget doesn't know or care where its products came from. The content team picks the data source in the CMS dropdown — the same carousel component renders personalized recommendations or editorial picks depending on configuration.









Audience Filtering: The Invisible Gatekeeper



Not every user should see every component. Guest users see a sign-in card. Loyalty members see exclusive offers. The CMS attaches audience rules to each component:




fragment audienceFields on AudienceRules {
premiumMember
loyaltyMember
dualMembership
loggedInUser
guestUser
}






During DTO→Entity conversion, we evaluate these rules against the current user's state. Dip built the audience resolution logic — clean, single-pass, no widget-level branching. If the user doesn't match, the entity's isVisible flag is set to false. The ListView checks this flag before rendering:




itemBuilder: (context, index) {
final widget = layout[index];
return widget.isVisible
? WidgetFactory.instance.createWidget(context, widget)
: const SizedBox.shrink();
}






Filtering happens once, at the data boundary — not in each component, not in the UI layer, not scattered across widgets. One place.









What We'd Do Differently



Preloading. Right now, each component fetches its own data when it scrolls into view. A component at position 8 doesn't start loading until the user scrolls there. We'd add viewport-aware preloading — start fetching components 2-3 positions ahead of the current scroll position.



Error boundaries per component. We handle errors gracefully, but a bad API response from one data source can still show an empty slot. We'd wrap each builder output in an error boundary widget that shows a minimal fallback instead of empty space.









Takeaways





  • Let the CMS define layout, not your code. If your home screen changes require app releases, you've coupled content to code.


  • Factory + Registry > switch statements. An enum-driven registry scales to 50 component types without touching the factory.


  • Isolate components with their own BLoCs. No shared state, no cascade failures, parallel development.


  • Filter at the data boundary, not the UI. Audience rules belong in the DTO→Entity conversion, not scattered across widgets.


  • Cache aggressively, refresh silently. Users don't care about freshness on a 200ms cold start. They care about seeing something immediately.



Our home screen went from "every change is a sprint" to "the content team ships layout changes before Dip and I finish our coffee."



The app doesn't know what components exist. And that's exactly when you know you've finally decoupled your UI.






Tags: Flutter, Mobile Development, Software Architecture, Clean Architecture, CMS

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Our Home Screen Renders 12 Components — Without Knowing Any of Them

Thematisch verwandte Begriffe: Home, Screen, Renders, Components · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-82000 | Adobe Experience Manager Forms JEE is affected by a Server-Side Request …
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick