Flutter Setup & First App

Project Structure & Configuration

45 min Lesson 3 of 12

Creating a New Flutter Project

Every Flutter journey starts with creating a new project. The flutter create command scaffolds a complete project with all the necessary files and directories for building apps on multiple platforms. Let’s explore how to create a project and understand every file and folder it generates.

Creating a Project with flutter create

# Basic project creation
flutter create my_app

# Create with a specific organization (reverse domain)
flutter create --org com.example my_app

# Create with specific platforms only
flutter create --platforms android,ios,web my_app

# Create a project with a specific language
flutter create --android-language kotlin --ios-language swift my_app

# Create different project types
flutter create --template=app my_app        # Full application (default)
flutter create --template=package my_pkg    # Dart package
flutter create --template=plugin my_plugin  # Platform plugin
flutter create --template=module my_module  # Add-to-app module
Note: Project names must be valid Dart identifiers: all lowercase, with underscores to separate words (snake_case). Names like my_app and flutter_todo are valid, but MyApp, my-app, and 123app are not.

Project Directory Layout

After running flutter create my_app, you get a project directory with the following structure. Understanding each file and folder is essential for effective Flutter development.

Complete Project Structure

my_app/
+-- android/              # Android-specific code and config
|   +-- app/
|   |   +-- build.gradle  # App-level build config
|   |   +-- src/
|   |       +-- main/
|   |           +-- AndroidManifest.xml
|   |           +-- kotlin/...  # Android native code
|   +-- build.gradle       # Project-level build config
|   +-- settings.gradle    # Gradle settings
+-- ios/                   # iOS-specific code and config
|   +-- Runner/
|   |   +-- AppDelegate.swift
|   |   +-- Info.plist     # iOS app configuration
|   |   +-- Assets.xcassets # App icons and images
|   +-- Runner.xcworkspace  # Xcode workspace
+-- lib/                   # YOUR DART CODE GOES HERE
|   +-- main.dart          # App entry point
+-- test/                  # Unit and widget tests
|   +-- widget_test.dart   # Default widget test
+-- web/                   # Web-specific files
|   +-- index.html         # Web entry point
|   +-- manifest.json      # Web app manifest
+-- linux/                 # Linux desktop files
+-- macos/                 # macOS desktop files
+-- windows/               # Windows desktop files
+-- pubspec.yaml           # PROJECT CONFIGURATION
+-- pubspec.lock           # Locked dependency versions
+-- analysis_options.yaml  # Dart analyzer rules
+-- .gitignore             # Git ignore rules
+-- README.md              # Project readme

The lib/ Directory

The lib/ directory is where all your Dart source code lives. This is the most important directory in your project. When your project grows, you will organize code into subdirectories here.

Typical lib/ Organization for Larger Projects

lib/
+-- main.dart              # Entry point
+-- app.dart               # MaterialApp configuration
+-- config/                # App configuration, themes, routes
|   +-- theme.dart
|   +-- routes.dart
+-- models/                # Data models
|   +-- user.dart
|   +-- product.dart
+-- screens/               # Full screen pages
|   +-- home_screen.dart
|   +-- profile_screen.dart
+-- widgets/               # Reusable widgets
|   +-- custom_button.dart
|   +-- product_card.dart
+-- services/              # API calls, database, etc.
|   +-- api_service.dart
|   +-- auth_service.dart
+-- providers/             # State management
|   +-- auth_provider.dart
+-- utils/                 # Utility functions and constants
    +-- constants.dart
    +-- helpers.dart
Tip: There is no enforced folder structure in Flutter. However, following a consistent pattern like the one above makes your code easier to navigate and maintain. Choose a structure early in your project and stick with it.

The main.dart Entry Point

Every Flutter app starts with the main.dart file inside the lib/ directory. The main() function is the entry point of the application, and it calls runApp() to start the Flutter framework.

Default main.dart

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '\$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Understanding runApp()

The runApp() function takes a widget and makes it the root of the widget tree. It inflates the given widget and attaches it to the screen. This widget becomes the top-level ancestor of all other widgets in your app.

How runApp() Works

// runApp() does the following:
// 1. Takes your root widget (e.g., MyApp())
// 2. Creates the root Element
// 3. Attaches it to the rendering pipeline
// 4. Triggers the first build/layout/paint cycle
// 5. Sets up the event loop for user interactions

void main() {
  // You can do initialization before runApp()
  WidgetsFlutterBinding.ensureInitialized();

  // For example: initialize Firebase, load config, etc.
  // await Firebase.initializeApp();

  runApp(const MyApp());
}

// The simplest possible Flutter app:
void main() {
  runApp(
    const Center(
      child: Text(
        'Hello, Flutter!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

The MaterialApp Widget

The MaterialApp widget is typically the root widget of a Flutter app that uses Material Design. It wraps several essential widgets that most apps need: navigation, theming, localization, and more.

MaterialApp Configuration

MaterialApp(
  // App identity
  title: 'My App',                    // Used by OS task switcher
  debugShowCheckedModeBanner: false,   // Remove debug banner

  // Theming
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
    useMaterial3: true,
    fontFamily: 'Roboto',
  ),
  darkTheme: ThemeData.dark(useMaterial3: true),
  themeMode: ThemeMode.system,         // Follow system setting

  // Navigation
  home: const HomeScreen(),            // Default route
  routes: {
    '/home': (context) => const HomeScreen(),
    '/profile': (context) => const ProfileScreen(),
    '/settings': (context) => const SettingsScreen(),
  },

  // Localization
  locale: const Locale('en', 'US'),
  supportedLocales: const [
    Locale('en', 'US'),
    Locale('ar', 'SA'),
  ],

  // Error handling
  builder: (context, child) {
    return MediaQuery(
      data: MediaQuery.of(context).copyWith(
        textScaler: TextScaler.noScaling,
      ),
      child: child!,
    );
  },
)
Note: If you prefer iOS-style widgets, use CupertinoApp instead of MaterialApp. For a fully custom design system, use WidgetsApp which provides the core functionality without any design language.

pubspec.yaml Deep Dive

The pubspec.yaml file is the heart of your Flutter project’s configuration. It defines your project’s name, dependencies, assets, fonts, and more. Understanding every section is crucial.

Complete pubspec.yaml Example

name: my_app
description: A Flutter application for task management.
publish_to: 'none'  # Remove this line if publishing to pub.dev
version: 1.0.0+1     # version: major.minor.patch+buildNumber

environment:
  sdk: '>=3.2.0 <4.0.0'  # Dart SDK version constraint

dependencies:
  flutter:
    sdk: flutter

  # UI packages
  cupertino_icons: ^1.0.6    # iOS-style icons
  google_fonts: ^6.1.0       # Google Fonts integration

  # State management
  provider: ^6.1.1           # Simple state management
  flutter_bloc: ^8.1.3       # BLoC pattern

  # Networking
  http: ^1.2.0               # HTTP requests
  dio: ^5.4.0                # Advanced HTTP client

  # Local storage
  shared_preferences: ^2.2.2 # Key-value storage
  hive: ^2.2.3               # NoSQL database

  # Utilities
  intl: ^0.19.0              # Internationalization
  url_launcher: ^6.2.2       # Open URLs

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.1      # Lint rules
  build_runner: ^2.4.7       # Code generation
  mockito: ^5.4.4            # Testing mocks

flutter:
  uses-material-design: true  # Enable Material icons

  # Asset declarations
  assets:
    - assets/images/          # Entire directory
    - assets/icons/           # Another directory
    - assets/data/config.json # Specific file

  # Custom font declarations
  fonts:
    - family: CustomFont
      fonts:
        - asset: assets/fonts/CustomFont-Regular.ttf
        - asset: assets/fonts/CustomFont-Bold.ttf
          weight: 700
        - asset: assets/fonts/CustomFont-Italic.ttf
          style: italic

Version Constraints

Understanding Version Constraints

# Dependency version syntax:
dependencies:
  # Caret syntax (recommended) - allows minor and patch updates
  provider: ^6.1.1        # >=6.1.1 <7.0.0

  # Range syntax - explicit range
  http: '>=1.0.0 <2.0.0'  # Same as ^1.0.0

  # Exact version (not recommended)
  dio: 5.4.0              # Exactly 5.4.0

  # Any version (dangerous)
  intl: any               # Any version, avoid this!

  # Git dependency
  my_package:
    git:
      url: https://github.com/user/repo.git
      ref: main            # branch, tag, or commit hash

  # Path dependency (local package)
  my_local_pkg:
    path: ../my_local_pkg

Managing Dependencies

Dependency Management Commands

# Add a dependency
flutter pub add provider
flutter pub add dio

# Add a dev dependency
flutter pub add --dev mockito
flutter pub add --dev build_runner

# Remove a dependency
flutter pub remove provider

# Get all dependencies (downloads them)
flutter pub get

# Upgrade dependencies to latest compatible versions
flutter pub upgrade

# Check for outdated packages
flutter pub outdated

# Resolve dependency conflicts
flutter pub upgrade --major-versions
Tip: Always use the caret syntax (^) for version constraints. It allows bug fixes and minor updates while preventing breaking changes. Run flutter pub outdated regularly to keep your dependencies up to date.

Working with Assets

Assets are files bundled with your app, such as images, fonts, JSON files, and other data. They must be declared in pubspec.yaml before they can be used.

Asset Directory Structure and Usage

# Directory structure:
my_app/
+-- assets/
|   +-- images/
|   |   +-- logo.png
|   |   +-- 2.0x/logo.png     # 2x resolution
|   |   +-- 3.0x/logo.png     # 3x resolution
|   +-- icons/
|   |   +-- home.svg
|   +-- data/
|       +-- countries.json

# pubspec.yaml declaration:
flutter:
  assets:
    - assets/images/
    - assets/icons/
    - assets/data/countries.json

# Using assets in Dart code:
// Images
Image.asset('assets/images/logo.png')

// JSON data
final String jsonString = await rootBundle.loadString(
  'assets/data/countries.json'
);
final data = json.decode(jsonString);
Warning: After adding new assets to pubspec.yaml, you must run flutter pub get and perform a hot restart (not just hot reload) for the changes to take effect. Hot reload cannot detect new asset declarations.

The .gitignore File

Flutter generates a comprehensive .gitignore file that excludes build artifacts, IDE files, and platform-specific generated files from version control.

Important .gitignore Entries

# Flutter/Dart specific
.dart_tool/           # Dart tooling cache
.packages             # Legacy package config
build/                # Build output directory
.flutter-plugins      # Generated plugin registrations
.flutter-plugins-dependencies

# IDE specific
.idea/                # Android Studio / IntelliJ
.vscode/              # VS Code (optional, some teams track this)
*.iml                 # IntelliJ module files

# Platform build artifacts
**/android/.gradle/
**/android/captures/
**/android/local.properties
**/ios/Pods/
**/ios/.symlinks/
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec

# Generated files
*.g.dart              # Build runner generated files
*.freezed.dart        # Freezed generated files
*.mocks.dart          # Mockito generated files
pubspec.lock          # Include this for apps, exclude for packages

# Sensitive files
.env                  # Environment variables
*.jks                 # Java keystores
*.keystore            # Android keystores
Note: For Flutter applications, commit pubspec.lock to version control to ensure all team members use the same dependency versions. For Flutter packages, do not commit pubspec.lock so that consumers can resolve their own dependency versions.

analysis_options.yaml

The analysis_options.yaml file configures the Dart analyzer, which provides static analysis, linting, and code quality checks. It helps catch errors and enforce coding standards before your code even runs.

analysis_options.yaml Configuration

include: package:flutter_lints/flutter.yaml

analyzer:
  exclude:
    - "**/*.g.dart"          # Exclude generated files
    - "**/*.freezed.dart"

  errors:
    invalid_annotation_target: ignore
    missing_required_param: error   # Treat as error
    missing_return: error           # Treat as error

  language:
    strict-casts: true       # No implicit casts
    strict-raw-types: true   # No raw generic types

linter:
  rules:
    # Style rules
    - prefer_const_constructors
    - prefer_const_declarations
    - prefer_final_fields
    - prefer_final_locals
    - avoid_print               # Use logging instead
    - sort_constructors_first
    - sort_unnamed_constructors_first

    # Safety rules
    - always_declare_return_types
    - avoid_dynamic_calls
    - cancel_subscriptions
    - close_sinks

    # Documentation
    - public_member_api_docs: false  # Disable for apps

build.gradle Basics (Android)

The build.gradle files configure the Android build process. There are two: a project-level file and an app-level file. You rarely need to modify these, but understanding them helps when adding Android-specific features.

android/app/build.gradle Key Sections

plugins {
    id "com.android.application"
    id "kotlin-android"
    id "dev.flutter.flutter-gradle-plugin"
}

android {
    namespace "com.example.my_app"
    compileSdk flutter.compileSdkVersion

    defaultConfig {
        applicationId "com.example.my_app"  // Unique app identifier
        minSdk flutter.minSdkVersion        // Minimum Android version
        targetSdk flutter.targetSdkVersion  // Target Android version
        versionCode 1                       // Internal version number
        versionName "1.0.0"                 // Display version string
    }

    buildTypes {
        release {
            signingConfig signingConfigs.debug  // Change for production!
            minifyEnabled true                  // Enable code shrinking
            proguardFiles getDefaultProguardFile(
                'proguard-android-optimize.txt'
            ), 'proguard-rules.pro'
        }
    }
}

flutter {
    source '../..'  // Points to the Flutter project root
}

dependencies {
    // Android-specific dependencies go here
}
Tip: When changing applicationId, minSdk, or targetSdk in build.gradle, also update the corresponding values in Flutter’s configuration. The applicationId is what uniquely identifies your app on the Google Play Store and must never change after publishing.

Running and Building Your App

Now that you understand the project structure, let’s review the essential commands for running and building your Flutter app.

Essential Flutter Commands

# Run in debug mode (with hot reload)
flutter run

# Run on a specific device
flutter devices                    # List available devices
flutter run -d chrome              # Run on Chrome
flutter run -d emulator-5554      # Run on specific emulator

# Run in release mode
flutter run --release

# Run in profile mode (for performance profiling)
flutter run --profile

# Build for production
flutter build apk                  # Android APK
flutter build appbundle            # Android App Bundle (for Play Store)
flutter build ios                  # iOS
flutter build web                  # Web
flutter build windows              # Windows desktop
flutter build macos                # macOS desktop
flutter build linux                # Linux desktop

# Clean build cache
flutter clean

# Analyze code for issues
flutter analyze

Summary

In this lesson, you learned how to create a Flutter project with flutter create and explored every file and directory in the generated project structure. You understood the role of lib/ for your Dart code, main.dart as the entry point, runApp() for bootstrapping, and MaterialApp for app-wide configuration. You mastered pubspec.yaml for dependency and asset management, learned about .gitignore for version control, analysis_options.yaml for code quality, and build.gradle for Android configuration. You are now ready to start building your Flutter application with confidence.