1 / 32
Start
if/else
Loops
ListView
Maps
Theme
Dark mode
Build
Week 4 · Day 9

Dynamic UI &
Theming

Make UI react to data. Render lists. Stop hardcoding colors. Flip dark mode in one tap.

if / else for / .map ListView ThemeData Dark mode Day 9

Press J to jump. Arrow keys to navigate.

Recap

Day 8 gave us static UI.

You can build a screen now. But everything is hardcoded. Today: react to data.

Widgets

Text, Container, Image

Layouts

Row, Column, Stack

setState

Redraw on change

Today → UI changes based on data. Different widgets for different states.

Conditionals · 1/5

Show A or B with a ternary

The simplest conditional UI. Pick one widget or another based on a bool.

Widget build(c) {
  return isLoggedIn
    ? HomeScreen()
    : LoginScreen();
}

Read it: "if logged in, show Home, else show Login." Common for auth flows.

Conditionals · live demo

Tap to flip the UI

Click below. Watch the whole card swap.

๐Ÿ”’
Please log in
You're not signed in yet

In Flutter: isLoggedIn ? Home() : Login()

Conditionals · 2/5

collection-if — show or skip

Inside a Column or Row's children:, you can use if to include a widget — or skip it entirely.

Column(
  children: [
    Text('Welcome!'),
    if (isPremium) Text('โญ Premium user'),
    Text('Have a great day'),
  ],
)

No SizedBox.shrink(). No empty containers. Just include or skip.

Conditionals · 3/5 · Visual

Same code, two outcomes

Toggle the flag. The premium row appears or disappears.

isPremium = false

Welcome!
Have a great day

isPremium = true

Welcome!
โญ Premium user
Have a great day
Conditionals · 4/5

Same widget, different look

You can also use ternary for properties. Color, size, anything.

Container(
  color: isError ? Colors.red : Colors.green,
  child: Text(message),
)
isError = false
isError = true
Loops · 1/4

collection-for — data → widgets

Got a list of names? Use for right inside children:.

var names = ['Anita', 'Raj', 'Mira'];

Column(
  children: [
    for (var name in names)
      Text(name),
  ],
)

3 names → 3 Text widgets. Add a 4th name? 4 widgets. Automatic.

Loops · 2/4

The .map() way

Same result. Different style. Functional flavor — like Dart's .map from Day 5.

Column(
  children: names
    .map((name) => Text(name))
    .toList(),
)

โš ๏ธ Don't forget .toList().map returns an Iterable, but Column needs a List.

Loops · 3/4

Which one to use?

collection-for

Cleaner. Reads like English.

โœ“ Most beginner-friendly
โœ“ Mix freely with other widgets
โœ“ Combine with collection-if

.map().toList()

Functional. Same as JS or Python.

โœ“ Familiar from other languages
โœ“ Chain with .where, .take
โœ— Easy to forget .toList()

In this course we'll mostly use collection-for. It's clearer.

Loops · 4/4 · Live demo

Add & remove items

Click to add. Watch the column grow. That's collection-for at work.

Lists · 1/4

ListView — scrollable lists

Column doesn't scroll. ListView does. Use it for anything taller than the screen.

ListView(
  children: [
    for (var p in products)
      ProductCard(p),
  ],
)
Lists · 2/4

ListView.builder — for big lists

Got 1000 items? Don't build them all at once. .builder creates rows only as you scroll.

ListView.builder(
  itemCount: products.length,
  itemBuilder: (c, i) {
    return ProductCard(products[i]);
  },
)

Lazy. Only renders rows on screen. Scroll smoothly even with 10,000 items.

Lists · 3/4

When to pick which

๐Ÿ“‹ ListView()

Few items, all at once.

โœ“ Settings page (~10 items)
โœ“ Mixed widget types
โœ“ Quick prototypes

โšก ListView.builder()

Many items, lazy.

โœ“ Feeds, chat, search results
โœ“ Anything with 50+ items
โœ“ Loading from API

Default to .builder. It's faster and you can swap data in/out.

Lists · 4/4

Empty list? Show something nice.

Don't show a blank screen. Combine ternary + ListView.

products.isEmpty
  ? EmptyState()
  : ListView.builder(...)
๐Ÿ“ญ
No products yet
Add your first one to get started
Working with Maps · 1/3

Real data = Map<String, dynamic>

APIs return JSON. Parsed JSON = Maps. You'll see this everywhere.

var product = {
  'name': 'Coffee',
  'price': 120,
  'inStock': true,
};

Text(product['name'])
Working with Maps · 2/3

List of Maps → List of cards

The pattern you'll use 100x in real apps.

[
  {name: 'โ˜•'},
  {name: '๐Ÿต'},
  {name: '๐Ÿฅ'},
]
data
โ†’
for (var p in list)
  ProductCard(p),
render
โ†’
โ˜•
Coffee
๐Ÿต
Tea
๐Ÿฅ
Croissant
Working with Maps · 3/3

Missing field? ?? to the rescue

JSON sometimes has gaps. Use ?? to provide a fallback.

Text(
  product['description'] ?? 'No description',
)

// price defaults to 0 if missing
Text('\$${product[\'price\'] ?? 0}')

Reads as: "this value, or fallback if it's null." Saves you from crashes.

Theming · 1/6

Stop hardcoding colors.

Hardcoded values everywhere = nightmare. Theme = define once, use everywhere.

๐Ÿ˜ฉ Without theme

color: Color(0xFF42B8F5),
color: Color(0xFF42B8F5),
color: Color(0xFF42B8F5),
color: Color(0xFF42B8F5),

Want to change brand color? Edit 47 files. ๐Ÿ˜ญ

๐Ÿ˜Ž With theme

color: theme.primary,
color: theme.primary,
color: theme.primary,
color: theme.primary,

Change brand color? Edit 1 line. โœจ

Theming · 2/6

ThemeData — your design system

Pass a ThemeData to MaterialApp. Every widget below it picks it up.

MaterialApp(
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.blue,
    ),
  ),
  home: MyHome(),
)

fromSeed generates a full palette from one color. Material 3 magic.

Theming · 3/6

The colorScheme roles

Don't think "blue" or "red." Think purpose. Each role has a meaning.

primary

Brand. Buttons. Links.

secondary

Accents. Floating buttons.

surface

Cards. Sheets. Dialogs.

error

Failures. Validation.

background

App canvas.

onPrimary

Text on primary BG.

Theming · 4/6

Pull theme out: Theme.of(context)

Inside any widget, ask the context for the theme. Then use the colors.

Widget build(context) {
  final theme = Theme.of(context);

  return Container(
    color: theme.colorScheme.primary,
    child: Text(
      'Hello',
      style: theme.textTheme.headlineMedium,
    ),
  );
}
Theming · 5/6

textTheme — type system

Same idea for fonts. Don't pick sizes by guess — use named roles.

Display
displayLarge / displayMedium / displaySmall
Headline
headlineLarge / Medium / Small
Body text โ€” what you read.
bodyLarge / Medium / Small
Caption / label text.
labelLarge / Medium / Small
Theming · 6/6 · Before/After

The cleanup

๐Ÿ˜ฉ Hardcoded

Container(
  color: Color(0xFF42B8F5),
  child: Text(
    'Hi',
    style: TextStyle(
      fontSize: 22,
      color: Colors.white,
    ),
  ),
)

๐Ÿ˜Ž Themed

Container(
  color: t.colorScheme.primary,
  child: Text(
    'Hi',
    style: t.textTheme.headlineMedium,
  ),
)

Same UI. Half the code. Updates everywhere.

Dark mode · 1/3

Two themes, one toggle

Pass theme AND darkTheme to MaterialApp. Then pick which to use.

MaterialApp(
  theme: ThemeData.light(),
  darkTheme: ThemeData.dark(),
  themeMode: ThemeMode.system, // or .light / .dark
  home: MyHome(),
)

.system follows the user's phone setting. Your app adapts automatically.

Dark mode · 2/3 · Live demo

Flip the switch

Click the toggle. Whole UI swaps. Zero hardcoded colors.

My Shop

โ˜• Coffee
Rs 120
๐Ÿต Tea
Rs 80
Dark mode · 3/3

Tips that save you pain

๐Ÿšซ

Don't hardcode

Colors.white breaks in dark mode. Use theme.

๐Ÿ–ผ๏ธ

Test images

Logos with white BG look weird on dark. Use SVG.

๐Ÿ”

Test both

Toggle every screen during dev. Catch contrast bugs.

Bonus

CupertinoTheme — iOS variant

If you used CupertinoApp on Day 8, theming is similar but separate. Skip if you're MaterialApp-only.

CupertinoApp(
  theme: CupertinoThemeData(
    primaryColor: CupertinoColors.systemBlue,
    brightness: Brightness.dark,
  ),
)

Most cross-platform apps stick with MaterialApp + dark mode. Cupertino theme = niche.

Build It · Step 1

Themed product list

Combine everything. List of Maps + ListView.builder + conditional badge + theme.

Menu
โ˜•
Espresso
Rs 120
IN STOCK
๐Ÿฅ
Croissant
Rs 80
OUT
๐Ÿต
Matcha
Rs 150
IN STOCK
Build It · Step 2

The whole thing in ~10 lines

final products = [
  {'name': 'Espresso', 'price': 120, 'inStock': true},
  {'name': 'Croissant', 'price': 80, 'inStock': false},
];

ListView.builder(
  itemCount: products.length,
  itemBuilder: (c, i) {
    final p = products[i];
    return ProductTile(
      name: p['name'],
      inStock: p['inStock'],
    );
  },
)
Recap

Today you learned

if/else in widgets

Ternary, collection-if, conditional styling

Loops

collection-for, .map().toList()

ListView

.builder for big lists, empty states

Map data

JSON-shaped data, ?? for nulls

ThemeData

colorScheme, textTheme, fromSeed

Dark mode

theme + darkTheme + themeMode

Homework

Build it.

Open DartPad or your editor. Ship it.

โ˜ Make a List<Map> of 5 products
โ˜ Render with ListView.builder
โ˜ Show "Out of stock" badge with if
โ˜ Define a custom ThemeData
โ˜ Replace hardcoded colors with Theme.of
โ˜ Add a ๐ŸŒ™ dark mode toggle
โ˜ Bonus: filter by category chips

Day 10: navigation, multiple screens, passing data between pages. See you there.