Home Flutter UI 102 Advanced UI Features

🎨 Advanced UI Features — Build an iOS-Style Rolodex App!

⏱️ 45-50 minutes 📊 Advanced — New design system! 📦 2 Projects included 🏷️ Cupertino, iOS, Data Models, Contacts, Theme

🧒 A Third App! What's a Rolodex? (And What's Cupertino?)

📇

Goodbye Wikipedia, Hello Rolodex — An iOS Contacts App!

A Rolodex is an old-school rotating card file for storing contacts. You're going to build a digital version — a contacts app that looks and feels like the built-in iPhone Contacts app!

This is the third app in your Flutter journey, and it introduces advanced UI concepts:

  • 🍎 Cupertino Design — Apple's iOS design language
  • 📱 Adaptive Layouts — Looks great on phones AND tablets
  • 📜 Slivers & Scrolling — Advanced scroll effects
  • 🧭 Stack Navigation — Push and pop screens
  • 🎨 Comprehensive Theming — Light AND dark mode!
🎯
Today you'll set up the foundation:
  • ✅ Create a Cupertino-based Flutter project
  • ✅ Understand Cupertino vs Material Design
  • ✅ Build Contact and ContactGroup data models
  • ✅ Set up state management with ValueNotifier
🍎🆚🤖

Cupertino vs Material — Two Design Languages, One Flutter

Flutter gives you two complete design systems to choose from:

🤖

Material Design

Google's design system. Used by Android apps. What you've been using so far! Widgets: MaterialApp, Scaffold, AppBar.

🍎

Cupertino Design

Apple's design system. Used by iOS apps. Widgets: CupertinoApp, CupertinoPageScaffold, CupertinoNavigationBar.

Both work everywhere! A Cupertino app runs on Android, web, and desktop too — it just looks like iOS. This is great for building cross-platform apps that feel native on iPhones.

📢

ValueNotifier — ChangeNotifier's Simpler Cousin

In the last topic, you used ChangeNotifier with multiple state variables. ValueNotifier is a simpler version that holds just one value:

ValueNotifier example
final notifier = ValueNotifier<int>(0);  // Holds an int, starts at 0
notifier.value = 42;                // Change the value — automatically notifies listeners!
print(notifier.value);             // Read the current value

It's like ChangeNotifier with training wheels — perfect when you only need to track one piece of data. Our Rolodex app uses it to track the list of contact groups!

1 Create the Rolodex Project

Start fresh with a brand new Flutter project. This time, we're building with Cupertino widgets!

Step 1.1 Create & Set Up the Project

Terminal — Create Rolodex Project
~ $ flutter create rolodex --empty
Creating project rolodex...
Wrote 13 files.
~ $ cd rolodex && flutter pub add cupertino_icons
+ cupertino_icons 1.0.8
Added cupertino_icons to pubspec.yaml

The cupertino_icons package gives you access to Apple-style icons (like the share button, back arrow, etc.).

Step 1.2 Create the Folder Structure

In VS Code, create these folders inside the lib directory:

  • lib/data/ — For data models (Contact, ContactGroup)
  • lib/screens/ — For screen widgets (we'll build these in later lessons)
  • lib/theme/ — For theme configuration

Right-click the lib folder → New Folder → create each one. This keeps your code organized as the app grows!

2 Replace main.dart — Welcome to Cupertino!

Replace the entire content of main.dart with the Cupertino starter code.

Step 2.1 The Cupertino Starter Code

🔍 CupertinoApp — Line by Line

lib/main.dart
import 'package:flutter/cupertino.dart';  // ← Cupertino, not Material!

void main() {
    runApp(const RolodexApp());
}

class RolodexApp extends StatelessWidget {
    const RolodexApp({super.key});

    @override
    Widget build(BuildContext context) {
        return const CupertinoApp(                    // ← CupertinoApp!
            title: 'Rolodex',
            theme: CupertinoThemeData(             // ← iOS-style theme
                barBackgroundColor: CupertinoDynamicColor.withBrightness(
                    color: Color(0xFFF9F9F9),    // Light mode color
                    darkColor: Color(0xFF1D1D1D),   // Dark mode color
                ),
            ),
            home: CupertinoPageScaffold(           // ← iOS-style page
                child: Center(child: Text('Hello Rolodex!')),
            ),
        );
    }
}

Key differences from MaterialApp:

  • CupertinoApp instead of MaterialApp — Uses iOS design language.
  • CupertinoThemeData instead of ThemeData — iOS-specific theme properties.
  • CupertinoDynamicColor.withBrightness(...) — Automatically switches between light and dark colors based on system settings!
  • CupertinoPageScaffold instead of Scaffold — iOS-style page structure.

3 Create the Contact Data Model

The Contact class represents one person with their name and a unique ID.

Step 3.1 Create lib/data/contact.dart

Create a new file lib/data/contact.dart with the Contact class and sample data:

lib/data/contact.dart
class Contact {
    Contact({
        required this.id,
        required this.firstName,
        this.middleName,
        required this.lastName,
        this.suffix,
    });

    final int id;
    final String firstName;
    final String? middleName;  // ← Optional (not everyone has one)
    final String lastName;
    final String? suffix;      // ← Optional (Jr., Sr., Esq., etc.)
}

Then add the sample contacts — a set of 52 diverse contacts with different name formats. You can copy them from the Flutter tutorial or create your own!

📄 Click to view sample contacts (52 people — copy-paste into contact.dart)
final johnAppleseed = Contact(id: 0, firstName: 'John', lastName: 'Appleseed');
final kateBell = Contact(id: 1, firstName: 'Kate', lastName: 'Bell');
final annaHaro = Contact(id: 2, firstName: 'Anna', lastName: 'Haro');
final danielHiggins = Contact(id: 3, firstName: 'Daniel', lastName: 'Higgins', suffix: 'Jr.');
// ... 48 more contacts ...
final jessicaEdwards = Contact(id: 50, firstName: 'Jessica', lastName: 'Edwards');

final Set<Contact> allContacts = { johnAppleseed, kateBell, /* ... all 52 contacts ... */ };

4 Create the ContactGroup & State Model

Contacts need to be organized into groups like "All iPhone" and "Friends".

Step 4.1 Create lib/data/contact_group.dart

Create a new file lib/data/contact_group.dart with the ContactGroup class, sorting logic, sample groups, and the ContactGroupsModel:

lib/data/contact_group.dart — Key classes
class ContactGroup {
    /* Holds a group of contacts (e.g., "Friends", "Work") */
    /* Has id, label, title, contacts list, and alphabetizedContacts getter */
}

class ContactGroupsModel {
    /* Manages state with ValueNotifier<List<ContactGroup>> */
    /* Has listsNotifier, findContactList(), dispose() */
}

The full code includes sorting by last name → first name → middle name, and an alphabetizedContacts getter that groups contacts by their first letter (A, B, C...).

5 Connect Data to Your App

Update main.dart to import the data and create a global state instance.

Step 5.1 Final main.dart Updates

Updated main.dart
import 'package:flutter/cupertino.dart';

import 'data/contact_group.dart';  // ← Import our data!

final contactGroupsModel = ContactGroupsModel();  // ← Global state

void main() {
    runApp(const RolodexApp());
}
// RolodexApp class stays the same for now
Run your app now! Use flutter run -d chrome. You should see "Hello Rolodex!" centered on screen with a light gray navigation bar area at the top. The app is ready for the real UI in the next lessons!

📝 What You Learned Today

Cupertino Design System

Used CupertinoApp, CupertinoThemeData, and CupertinoPageScaffold — iOS-style widgets that work on all platforms.

Dark & Light Theme

Used CupertinoDynamicColor.withBrightness() to automatically switch colors based on system dark/light mode.

Data Models

Created Contact (with optional middleName/suffix) and ContactGroup (with alphabetized sorting).

ValueNotifier State

Used ValueNotifier<List<ContactGroup>> — a simpler ChangeNotifier that holds a single value.

🧠 Test Yourself!

Q1 What is the main difference between CupertinoApp and MaterialApp?

Q2 What is the purpose of a ValueNotifier in state management?

📦 Project 1: Add 5 of Your Own Contacts

Project 1Beginner

Objective: Customize the contact list with people you know

📋 Requirements:

  1. Open lib/data/contact.dart
  2. Add 5 new Contact instances — use names of your friends, family, or fictional characters
  3. Make sure each has a unique ID (continue from 51, 52, 53...)
  4. Try to include variety:
    • At least one with a middleName
    • At least one with a suffix (Jr., Sr., III, etc.)
  5. Add them to the allContacts Set

🎯 Expected:

Your Rolodex now has 57 contacts total, and some of them are people you know personally!

📦 Project 2: Add a "Family" Contact Group

Project 2Intermediate

Objective: Create a new contact group with your custom contacts

📋 Requirements:

  1. In contact_group.dart, create a new ContactGroup called family
  2. Give it id: 3 and label: 'Family'
  3. Add 3 of your custom contacts (from Project 1) to it
  4. Add family to the list returned by generateSeedData()
  5. Run the app — your new group should be in the data (even though we can't see it in the UI yet!)

🎯 Expected:

You now have 4 contact groups: All iPhone, Friends, Work, and Family. The data is ready for the UI in the next lesson!

🚀 What's Next?

Your Rolodex project is set up with Cupertino, data models, and state management! In the next lesson:

  • Adaptive Layouts — Make your app look great on phones AND tablets
  • Use LayoutBuilder to respond to screen size
  • Build the contacts list UI with alphabetized sections
Next Lesson: Adaptive Layouts →
🎉

Lesson Complete!

You set up a brand new iOS-style Flutter app with Cupertino widgets, contact data models, and ValueNotifier state management. The Rolodex app foundation is ready!

Click the button above to track your progress!