Set Everything Up
Today you're setting up your machine for Flutter development. Install Flutter using the official guide for your OS, then install VS Code and add the Flutter and Dart extensions. Run flutter doctor until everything has a green tick. Then create your first project and run it.
flutter create my_app cd my_app flutter run
flutter doctor --android-licenses and accept them all.Variables and Data Types
Open a new Dart file. Learn var, int, double, String, and bool. Learn what print() does. Write variables for your name, age, city, and height and print them all.
void main() {
var name = "John";
int age = 24;
double height = 5.9;
String city = "London";
bool isLearning = true;
print("Name: $name");
print("Age: $age");
print("City: $city");
print("Learning Flutter: $isLearning");
}
var name = "John" or String name = "John" — both work. Start with var to keep things simple.Functions and If/Else
Learn how to write a function in Dart — what void means, how to pass a value in, and how to return a value out. Write a function called checkAge that takes a number and prints "Adult" if it's 18 or above, and "Minor" if not.
void checkAge(int age) {
if (age >= 18) {
print("Adult");
} else {
print("Minor");
}
}
void main() {
checkAge(24); // John
checkAge(15); // Sarah
}
Lists and Loops
Learn how to create a List in Dart and how to loop through it using a for loop and forEach. Create a list of 5 names and print each one with its number.
void main() {
List<String> students = ["John", "Sarah", "Emma", "Chris", "Tom"];
for (int i = 0; i < students.length; i++) {
print("${i + 1}. ${students[i]}");
}
// Cleaner way
students.forEach((name) {
print("Hello, $name!");
});
}
Classes and Objects
Learn what a class is — think of it as a blueprint. Create a Student class with a name, age, and a method called introduce() that prints a greeting. Then create two Student objects and call introduce() on both.
class Student {
String name;
int age;
Student(this.name, this.age);
void introduce() {
print("Hi, I'm $name and I'm $age years old.");
}
}
void main() {
Student john = Student("John", 24);
Student sarah = Student("Sarah", 22);
john.introduce();
sarah.introduce();
}
Your First Flutter Widget
Open your Flutter project. You'll see main.dart already has a class extending StatelessWidget. That's Flutter using everything you learned in Week 1 — classes, methods, and objects — to build a screen. Today you'll understand what's already there and then build your own simple screen.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("John's App")),
body: Center(
child: Text(
"Hello, I'm learning Flutter!",
style: TextStyle(fontSize: 20),
),
),
),
);
}
}
Layouts: Column, Row, Container
Learn Column (stacks things top to bottom), Row (places things side by side), Container (a box you can size, color, and pad), and SizedBox (adds space between things). Build a simple profile card using these four widgets.
Column(
children: [
Container(
color: Colors.blue,
padding: EdgeInsets.all(16),
child: Text("John Smith",
style: TextStyle(color: Colors.white,
fontSize: 22, fontWeight: FontWeight.bold)),
),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Age: 24"),
SizedBox(width: 20),
Text("City: London"),
],
),
],
)
Styling Text and Colors
Learn TextStyle — fontSize, fontWeight, color, letterSpacing. Learn BoxDecoration — background color, rounded corners, shadows. Take yesterday's profile card and make it look noticeably better.
Container(
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(
color: Colors.black12, blurRadius: 6)],
),
padding: EdgeInsets.all(20),
child: Column(
children: [
Text("Sarah Connor",
style: TextStyle(fontSize: 24,
fontWeight: FontWeight.bold,
letterSpacing: 1.2)),
SizedBox(height: 4),
Text("Flutter Developer",
style: TextStyle(fontSize: 14,
color: Colors.grey)),
],
),
)
Buttons and Text Input
Learn ElevatedButton, TextField, onPressed, and TextEditingController. Build a screen with a name input field and a button. When tapped, show a greeting on screen using the name that was typed.
class GreetPage extends StatefulWidget {
@override
_GreetPageState createState() => _GreetPageState();
}
class _GreetPageState extends State<GreetPage> {
TextEditingController nameController =
TextEditingController();
String greeting = "";
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: nameController,
decoration: InputDecoration(
labelText: "Enter your name"),
),
ElevatedButton(
onPressed: () {
setState(() {
greeting = "Hello, ${nameController.text}!";
});
},
child: Text("Greet Me"),
),
Text(greeting, style: TextStyle(fontSize: 20)),
],
);
}
}
Lists on Screen
Learn ListView.builder and ListTile. ListView.builder only renders what's visible on screen, so it stays fast even with long lists. Build a scrollable list of students with a name and skill shown in each row.
List<Map<String, String>> students = [
{"name": "John", "skill": "Dart"},
{"name": "Sarah", "skill": "UI Design"},
{"name": "Emma", "skill": "Firebase"},
{"name": "Chris", "skill": "State Management"},
{"name": "Tom", "skill": "Animations"},
];
ListView.builder(
itemCount: students.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: Text(students[index]["name"]![0])),
title: Text(students[index]["name"]!),
subtitle: Text(students[index]["skill"]!),
);
},
)
🏆 Profile App
Build a personal profile app with 3 sections: a header with your name and avatar, a styled bio section with BoxDecoration and TextStyle, and a scrollable list of your skills at the bottom using ListView.builder.
Understanding State
Learn setState() and understand when to use StatefulWidget. Build a like button that toggles between a filled red heart and an empty grey heart each time it's tapped.
class LikeButton extends StatefulWidget {
@override
_LikeButtonState createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool isLiked = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(
isLiked ? Icons.favorite : Icons.favorite_border,
color: isLiked ? Colors.red : Colors.grey,
size: 36,
),
onPressed: () {
setState(() {
isLiked = !isLiked;
});
},
);
}
}
Forms and Validation
Learn Form, TextFormField, and validators. Build a login form with an email and password field. Show red error messages if fields are empty or the email has no @ sign.
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "Email"),
validator: (value) {
if (value == null || value.isEmpty)
return "Enter your email";
if (!value.contains("@"))
return "Enter a valid email";
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print("Welcome, John!");
}
},
child: Text("Login"),
),
],
),
)
Navigation Between Screens
Learn Navigator.push() and Navigator.pop(). Create two screens — a Home screen and a Profile screen. Put a button on Home that opens Profile, and a back button on Profile that returns to Home.
// Home Screen — button to navigate
ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => ProfileScreen()),
);
},
child: Text("View John's Profile"),
)
// Profile Screen
class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Profile")),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text("Go Back"),
),
),
);
}
}
Passing Data Between Screens
Build a list of 5 students. When you tap a student's name, open a detail screen that shows that student's full info. Pass the data through the Navigator using the destination widget's constructor.
class Student {
String name;
String skill;
Student(this.name, this.skill);
}
// Tapping a list item
ListTile(
title: Text(student.name),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) =>
StudentDetail(student: student)));
},
)
// Detail Screen
class StudentDetail extends StatelessWidget {
final Student student;
const StudentDetail({required this.student});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(student.name)),
body: Center(
child: Text("Skill: ${student.skill}",
style: TextStyle(fontSize: 20))),
);
}
}
Bottom Navigation and Tabs
Learn BottomNavigationBar and how to switch between 3 screens using it. Track which tab is active using a state variable. Each tab should show different content with its own icon and label.
class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 0;
final screens = [HomeScreen(), SearchScreen(), ProfileScreen()];
@override
Widget build(BuildContext context) {
return Scaffold(
body: screens[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (i) => setState(() => _currentIndex = i),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(
icon: Icon(Icons.search), label: "Search"),
BottomNavigationBarItem(
icon: Icon(Icons.person), label: "Profile"),
],
),
);
}
}
🏆 To-Do App
Build a to-do app: a home screen listing tasks, an Add Task screen you navigate to, the ability to mark tasks done with a strikethrough, and a delete button per task. Use setState to manage everything.
Fetch Data from the Internet
Add the http package to your project. Learn what async, await, and Future mean — they're how Dart handles things that take time. Fetch a list of posts from a free test API and print them in the debug console first.
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<void> fetchPosts() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
);
if (response.statusCode == 200) {
List data = jsonDecode(response.body);
print("John fetched ${data.length} posts!");
} else {
print("Something went wrong.");
}
}
Show API Data on Screen
Create a Post model class with a fromJson() constructor. Use FutureBuilder to show a loading spinner while data loads and a ListView when it's ready.
class Post {
final int id;
final String title;
Post({required this.id, required this.title});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(id: json['id'], title: json['title']);
}
}
FutureBuilder<List<Post>>(
future: fetchPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return CircularProgressIndicator();
if (snapshot.hasError)
return Text("Error loading posts");
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) =>
ListTile(title: Text(snapshot.data![index].title)),
);
},
)
Save Data Locally
Add the shared_preferences package. Learn how to save and read a value on the device. Build a simple flow: ask for the user's name on first launch, save it, and greet them by name every time after that.
import 'package:shared_preferences/shared_preferences.dart';
Future<void> saveName(String name) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', name);
}
Future<String> loadName() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('username') ?? "Guest";
}
void main() async {
await saveName("John");
String name = await loadName();
print("Welcome back, $name!"); // Welcome back, John!
}
Handle Errors and Loading States
Learn try/catch for catching errors. Add a loading spinner while data fetches, and a friendly error message with a retry button if something goes wrong.
Future<List<Post>> fetchPosts() async {
try {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
);
if (response.statusCode == 200) {
List json = jsonDecode(response.body);
return json.map((e) => Post.fromJson(e)).toList();
} else {
throw Exception("Server error");
}
} catch (e) {
throw Exception("Could not connect. Check internet.");
}
}
// In your FutureBuilder:
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
if (snapshot.hasError)
return Center(child: Column(children: [
Text("Something went wrong."),
ElevatedButton(
onPressed: () => setState(() {}),
child: Text("Try Again")),
]));
Review, Polish, and What's Next
Go through everything you built this week. Fix any bugs, remove unused variables, clean up print statements, and make sure all screens look consistent. Then spend 15 minutes thinking about what you've built over 4 weeks and what you want to build next.
// Remove debug prints
// print("John debug check"); ← delete these
// Remove unused variables
// String unused = "nothing"; ← delete these
// Use named constants instead of numbers
const double spacing = 16.0;
SizedBox(height: spacing)
// Hot reload → press r in terminal
// Hot restart → press R in terminal
🏆 Weather or News App
Build an app that fetches live data from a free API, displays it in a clean list, opens a detail screen on tap, saves the last search using shared_preferences, and shows a loading spinner and error message. This is proof of everything you learned in 4 weeks.
🎉 You Made It
You now know Dart, can build multi-screen Flutter apps, connect to real APIs, save data locally, and handle errors. That is a real, working foundation.
From here — look into Provider or Riverpod for managing bigger app state, try Firebase for a backend, or just pick a personal project you actually care about and build it. The best next step is always a real project.