Self-Learning Plan

4 Weeks · 5 Days a Week · 1 Hour a Day — from zero to building real Flutter apps.

4 Weeks
20 Days
3 Mini Projects
1 Capstone App

Before You Start

You need Flutter installed, VS Code set up, and about 1 hour free each day. Follow one day at a time. Don't skip days. If something doesn't work, Google the error, fix it, and move on — that's part of learning.

Your Progress0 / 20 days
Week 1

Learn Dart, the Language Flutter Uses

By Friday you'll understand how Dart works and be ready to write your first Flutter screens.

DAY 01

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.

Terminal
flutter create my_app
cd my_app
flutter run
You're done when: You see the default Flutter counter app running on your emulator or browser.
Tip: If flutter doctor shows Android license warnings, run flutter doctor --android-licenses and accept them all.
DAY 02

Variables and Data Types

Why it matters: Every app stores information — names, numbers, true/false values. This is how Dart does it.

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.

Dart
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");
}
You're done when: You can declare 5 different variables and print them without errors.
Tip: In Dart, you can write var name = "John" or String name = "John" — both work. Start with var to keep things simple.
DAY 03

Functions and If/Else

Why it matters: Functions let you reuse logic. If/else lets your app make decisions.

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.

Dart
void checkAge(int age) {
  if (age >= 18) {
    print("Adult");
  } else {
    print("Minor");
  }
}

void main() {
  checkAge(24);  // John
  checkAge(15);  // Sarah
}
You're done when: Your function correctly prints the right word for different ages.
Tip: If you're confused by return types, just use void for now — it means this function doesn't return anything.
DAY 04

Lists and Loops

Why it matters: Almost every app shows a list of something — messages, tasks, contacts.

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.

Dart
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!");
  });
}
You're done when: Your loop prints every item in the list with its number.
Tip: forEach is cleaner to write but a regular for loop gives you the index number. Use whichever feels natural.
DAY 05

Classes and Objects

Why it matters: Flutter is built around objects. Understanding classes now will save you a lot of confusion later.

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.

Dart
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();
}
You're done when: Both students introduce themselves with their own info.
Tip: Everything in Flutter is built from classes — every widget, every screen, every button. What you learned today is exactly how Flutter works under the hood.
Week 2

Build Real Screens with Flutter

By Friday you'll be able to build a real-looking app screen from scratch.

DAY 06

Your First Flutter Widget

Why it matters: In Flutter, a widget is just a class — exactly like the Student class you wrote yesterday.

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.

Flutter / Dart
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),
          ),
        ),
      ),
    );
  }
}
You're done when: Your screen shows your text with no red errors.
Tip: Notice that MyApp is just a class with a build method — exactly like the classes you wrote in Week 1. Flutter is just Dart.
DAY 07

Layouts: Column, Row, Container

Why it matters: Knowing Column and Row is like knowing how to hold a pencil — everything else depends on it.

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.

Flutter / Dart
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"),
      ],
    ),
  ],
)
You're done when: Your screen shows a profile card with a name, age, and city laid out properly.
Tip: When something doesn't align right, check mainAxisAlignment and crossAxisAlignment on your Column or Row.
DAY 08

Styling Text and Colors

Why it matters: A working app that looks bad won't feel good to use. Small styling changes make a huge difference.

Learn TextStyle — fontSize, fontWeight, color, letterSpacing. Learn BoxDecoration — background color, rounded corners, shadows. Take yesterday's profile card and make it look noticeably better.

Flutter / Dart
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)),
    ],
  ),
)
You're done when: Your card has a background, rounded corners, and styled text.
Tip: Use Colors.blue.shade100 for lighter shades — much easier than hex codes when starting out.
DAY 09

Buttons and Text Input

Why it matters: An app that can't take input isn't really an app.

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.

Flutter / Dart
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)),
      ],
    );
  }
}
You're done when: Typing a name and tapping the button shows a greeting on screen.
Tip: If the greeting doesn't appear, you've forgotten setState() — without it Flutter won't redraw the screen.
DAY 10

Lists on Screen

Why it matters: Almost every real app has a scrollable list — emails, messages, products, tasks.

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.

Flutter / Dart
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"]!),
    );
  },
)
You're done when: A scrollable list of students appears with a name and skill per row.
Tip: Always use ListView.builder instead of ListView when your list has more than a few items.
Week 2 Mini Project

🏆 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.

Week 3

State Management and Navigation

By Friday your apps will have multiple screens and respond to what users do.

DAY 11

Understanding State

Why it matters: State is the reason apps feel alive. Without it nothing on screen ever changes.

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.

Flutter / Dart
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;
        });
      },
    );
  }
}
You're done when: Tapping the button visibly changes the icon color and style every time.
Tip: The most common beginner mistake is changing a variable without putting it inside setState(() { }) — the screen just won't update.
DAY 12

Forms and Validation

Why it matters: Every app that collects info needs to check if that info is valid before using it.

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.

Flutter / Dart
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"),
      ),
    ],
  ),
)
You're done when: Your form shows red error messages for wrong inputs and passes when inputs are correct.
Tip: A validator returns a String (the error message) if something is wrong, or null if everything is fine.
DAY 13

Navigation Between Screens

Why it matters: Real apps have more than one screen. Navigation is how you move between them.

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.

Flutter / Dart
// 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"),
        ),
      ),
    );
  }
}
You're done when: You can go forward and back between two screens without errors.
Tip: Navigator.pop(context) just means go back. The AppBar back arrow does this automatically too.
DAY 14

Passing Data Between Screens

Why it matters: Navigation is only useful when you can carry information with you.

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.

Flutter / Dart
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))),
    );
  }
}
You're done when: Tapping each student opens their own detail screen with the correct information.
Tip: You pass data the same way you pass any value to a Dart class — through the constructor.
DAY 15

Bottom Navigation and Tabs

Why it matters: Most popular apps use bottom navigation. You should know how to build it.

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.

Flutter / Dart
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"),
        ],
      ),
    );
  }
}
You're done when: Tapping each icon at the bottom switches to the right screen.
Tip: The currentIndex variable connects the bar to the screen list — if they get out of sync, you'll see the wrong screen.
Week 3 Mini Project

🏆 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.

Week 4

APIs, Storage, and Final Project

By Friday you'll load real data from the internet, save data locally, and handle errors properly.

DAY 16

Fetch Data from the Internet

Why it matters: Almost every real app gets its data from somewhere online. This is how you do it.

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.

Flutter / Dart
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.");
  }
}
You're done when: You see real data from the internet printed in your debug console.
Tip: async/await just means "wait for this to finish before moving on" — like placing a food order and waiting for it before eating.
DAY 17

Show API Data on Screen

Why it matters: Getting data is step one. Showing it in a clean list is what users actually see.

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.

Flutter / Dart
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)),
    );
  },
)
You're done when: Your app shows a scrollable list of real posts loaded from the internet.
Tip: FutureBuilder has three states to handle — waiting, error, and done. Always handle all three.
DAY 18

Save Data Locally

Why it matters: Apps need to remember things between sessions — your name, your settings, whether you've logged in before.

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.

Flutter / Dart
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!
}
You're done when: You close the app, reopen it, and your saved name still appears.
Tip: ?? "Guest" means if nothing was saved yet, use Guest as the default. It's a fallback value.
DAY 19

Handle Errors and Loading States

Why it matters: The internet goes down. APIs fail. Your app needs to handle all of this without crashing.

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.

Flutter / Dart
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")),
  ]));
You're done when: Your app shows a spinner while loading, real data when done, and a retry button if it fails.
Tip: try/catch works like this — try runs your code, catch handles it if anything goes wrong.
DAY 20

Review, Polish, and What's Next

Why it matters: Shipping something clean matters more than building something messy.

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.

Dart — cleanup checklist
// 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
You're done when: Your Week 4 project runs without errors and looks presentable.
Tip: Hot reload (r) applies your code changes instantly. Hot restart (R) fully restarts but is still much faster than stopping and re-running.
Week 4 Capstone Project

🏆 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.

What to explore next: