Flutter Setup & First App

Your First Flutter App

55 min Lesson 5 of 12

Creating Your First Flutter Project

Now that you have Flutter installed and configured, it’s time to build your first application. Flutter provides a default counter app template that demonstrates core concepts like widgets, state management, and the widget tree. In this lesson, we will create, explore, modify, and run this app step by step.

Creating a New Flutter Project

flutter create my_first_app
cd my_first_app

This command generates a complete Flutter project with a well-organized folder structure. The main file we will focus on is lib/main.dart, which contains the entry point for the application.

Note: Flutter project names must be lowercase with underscores (snake_case). For example, my_first_app is valid, but MyFirstApp or my-first-app are not.

Understanding the Default Counter App

When you open lib/main.dart, you will see the default counter app. Let’s break it down piece by piece to understand every part.

The main() Function

import 'package:flutter/material.dart';

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

The main() function is the entry point of every Dart application. The runApp() function takes a widget and makes it the root of the widget tree. Every Flutter app starts here.

MaterialApp -- The Foundation

The MaterialApp widget is the top-level widget that wraps your entire application. It provides Material Design styling, navigation, theming, and many other features out of the box.

MyApp StatelessWidget

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'),
    );
  }
}

Key properties of MaterialApp:

  • title -- The application title shown in task switchers
  • theme -- Defines the visual appearance (colors, fonts, shapes)
  • home -- The widget displayed when the app starts
  • useMaterial3: true -- Enables the latest Material Design 3 styling
Tip: ColorScheme.fromSeed() generates a complete color palette from a single seed color. This is a Material Design 3 feature that ensures consistent, accessible colors throughout your app.

Scaffold -- The Page Structure

The Scaffold widget provides the basic visual layout structure for a Material Design page. It gives you an app bar, a body area, a floating action button, drawers, and more.

MyHomePage StatefulWidget

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

  final String title;

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

This is a StatefulWidget, which means it can hold and change state over time. The createState() method returns the associated State object where the mutable data and build logic live.

State, setState(), and the Build Method

The State class is where the magic happens. It holds the mutable data (_counter) and defines how the UI is built and rebuilt when state changes.

The State Class

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),
      ),
    );
  }
}
Important: Always call setState() when changing state variables. Without it, Flutter will not know that the UI needs to be rebuilt, and your changes will not appear on screen. Never modify state variables outside of setState() in a StatefulWidget.

Understanding Key Widgets

Let’s examine each widget used in the counter app:

AppBar

The AppBar widget creates a toolbar at the top of the screen. It typically contains a title, navigation icons, and action buttons.

AppBar Properties

AppBar(
  backgroundColor: Theme.of(context).colorScheme.inversePrimary,
  title: Text(widget.title),
  // Optional properties:
  // leading: IconButton(icon: Icon(Icons.menu), onPressed: () {}),
  // actions: [IconButton(icon: Icon(Icons.search), onPressed: () {})],
  // centerTitle: true,
  // elevation: 4.0,
)

Center, Column, and Layout

The Center widget centers its child within itself. Column arranges its children vertically. Together they place content in the middle of the screen stacked top to bottom.

Text Widget

The Text widget displays a string of text with a single style. You can customize it with the style parameter.

Text Widget Examples

// Simple text
const Text('Hello, Flutter!')

// Styled text
Text(
  'Counter: \$_counter',
  style: Theme.of(context).textTheme.headlineMedium,
)

// Custom styled text
Text(
  'Custom Text',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
  ),
)

FloatingActionButton

The FloatingActionButton (FAB) is a circular button that floats above the content. It is typically used for the primary action on a screen.

FloatingActionButton Properties

FloatingActionButton(
  onPressed: _incrementCounter,  // Function called on tap
  tooltip: 'Increment',          // Shown on long press
  child: const Icon(Icons.add),   // The icon displayed
  // Optional properties:
  // backgroundColor: Colors.green,
  // foregroundColor: Colors.white,
  // mini: true,  // Smaller version
)

Modifying the Counter App

Now let’s customize the app. We will change colors, add a decrement button, and modify the text.

Changing the Color Scheme

Custom Theme Colors

MaterialApp(
  title: 'My Counter App',
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
    useMaterial3: true,
  ),
  home: const MyHomePage(title: 'My Counter App'),
)

Adding a Decrement Button

Let’s add a second button to decrease the counter. We will also add a reset button.

Modified State with Multiple Actions

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

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

  void _decrementCounter() {
    setState(() {
      _counter--;
    });
  }

  void _resetCounter() {
    setState(() {
      _counter = 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _resetCounter,
            tooltip: 'Reset',
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('Counter Value:'),
            Text(
              '\$_counter',
              style: Theme.of(context).textTheme.displayMedium?.copyWith(
                color: _counter >= 0 ? Colors.teal : Colors.red,
              ),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton.icon(
                  onPressed: _decrementCounter,
                  icon: const Icon(Icons.remove),
                  label: const Text('Decrease'),
                ),
                const SizedBox(width: 16),
                ElevatedButton.icon(
                  onPressed: _incrementCounter,
                  icon: const Icon(Icons.add),
                  label: const Text('Increase'),
                ),
              ],
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
Tip: The SizedBox widget is commonly used to add spacing between widgets. SizedBox(height: 20) adds 20 logical pixels of vertical space, and SizedBox(width: 16) adds horizontal space.

Running the App

There are several ways to run your Flutter application:

Using the Terminal

Running from the Command Line

# Run on the default device
flutter run

# Run on a specific device
flutter run -d chrome          # Web browser
flutter run -d emulator-5554   # Android emulator
flutter run -d iPhone          # iOS simulator

# List available devices first
flutter devices

Using VS Code

In VS Code, you can press F5 or go to Run > Start Debugging. Make sure you have a device selected in the bottom status bar. The Flutter extension provides a convenient device selector.

Using Android Studio

In Android Studio, select a device from the device dropdown in the toolbar, then click the green Run button or press Shift + F10.

Note: The first time you run a Flutter app, it may take several minutes to build. Subsequent runs will be much faster due to incremental compilation and the Dart VM’s hot reload capability.

Understanding the Widget Tree

Every Flutter app is built from a tree of widgets. For our counter app, the widget tree looks like this:

Widget Tree Structure

MaterialApp
  > MyHomePage (StatefulWidget)
    > Scaffold
      > AppBar
        > Text (title)
      > Center (body)
        > Column
          > Text ('Counter Value:')
          > Text (_counter)
          > SizedBox
          > Row
            > ElevatedButton (Decrease)
            > SizedBox
            > ElevatedButton (Increase)
      > FloatingActionButton
        > Icon (add)

Understanding the widget tree is fundamental. Every visible element on screen is a widget, and widgets are composed together in a tree hierarchy. When setState() is called, Flutter rebuilds the affected portion of the tree efficiently.

Adding Custom Styling

Let’s enhance our app with some additional styling to make it more visually appealing.

Enhanced Body with Custom Styling

body: Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
      colors: [
        Theme.of(context).colorScheme.primaryContainer,
        Theme.of(context).colorScheme.surface,
      ],
    ),
  ),
  child: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Icon(
          Icons.touch_app,
          size: 48,
          color: Theme.of(context).colorScheme.primary,
        ),
        const SizedBox(height: 16),
        const Text(
          'Tap the buttons to change the counter',
          style: TextStyle(fontSize: 16),
        ),
        const SizedBox(height: 24),
        Container(
          padding: const EdgeInsets.all(24),
          decoration: BoxDecoration(
            color: Theme.of(context).colorScheme.surface,
            borderRadius: BorderRadius.circular(16),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withOpacity(0.1),
                blurRadius: 10,
                offset: const Offset(0, 4),
              ),
            ],
          ),
          child: Text(
            '\$_counter',
            style: Theme.of(context).textTheme.displayLarge,
          ),
        ),
      ],
    ),
  ),
),

Common Beginner Mistakes

Here are common mistakes new Flutter developers make and how to avoid them:

  • Forgetting const constructors: Use const for widgets that never change to improve performance
  • Modifying state without setState(): The UI will not update unless you wrap changes in setState()
  • Missing commas: Dart uses trailing commas extensively -- add them after every parameter for better formatting
  • Forgetting to import: Material widgets require import 'package:flutter/material.dart';
Common Mistake: Do not perform heavy computations or async operations inside setState(). The callback passed to setState() should only update state variables. Perform your logic first, then call setState() to update the UI.

Summary

In this lesson you learned how to:

  • Create a new Flutter project with flutter create
  • Understand the structure of the default counter app
  • Use MaterialApp, Scaffold, AppBar, and FloatingActionButton
  • Work with StatefulWidget and setState()
  • Modify the app by changing colors, adding buttons, and customizing text
  • Run the app on different devices using the terminal or IDE
  • Understand the widget tree hierarchy

Practice Exercise

Modify the counter app to include: (1) A title that displays “My Custom Counter” in the app bar, (2) The counter value changes color based on whether it is positive (green), negative (red), or zero (grey), (3) Three buttons: increment by 1, decrement by 1, and increment by 5, (4) A reset button in the app bar actions. Test the app on your preferred device or emulator.