🎨 Advanced UI Features — Build an iOS-Style Rolodex App!
🧒 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!
- ✅ 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:
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
✓ Wrote 13 files.
✓ 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
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:
CupertinoAppinstead ofMaterialApp— Uses iOS design language.CupertinoThemeDatainstead ofThemeData— iOS-specific theme properties.CupertinoDynamicColor.withBrightness(...)— Automatically switches between light and dark colors based on system settings!CupertinoPageScaffoldinstead ofScaffold— 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:
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:
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
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
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
Objective: Customize the contact list with people you know
📋 Requirements:
- Open
lib/data/contact.dart - Add 5 new Contact instances — use names of your friends, family, or fictional characters
- Make sure each has a unique ID (continue from 51, 52, 53...)
- Try to include variety:
- At least one with a middleName
- At least one with a suffix (Jr., Sr., III, etc.)
- Add them to the
allContactsSet
🎯 Expected:
Your Rolodex now has 57 contacts total, and some of them are people you know personally!
📦 Project 2: Add a "Family" Contact Group
Objective: Create a new contact group with your custom contacts
📋 Requirements:
- In
contact_group.dart, create a newContactGroupcalledfamily - Give it
id: 3andlabel: 'Family' - Add 3 of your custom contacts (from Project 1) to it
- Add
familyto the list returned bygenerateSeedData() - 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!
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!