Your First Flutter App
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.
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 switcherstheme-- Defines the visual appearance (colors, fonts, shapes)home-- The widget displayed when the app startsuseMaterial3: true-- Enables the latest Material Design 3 styling
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),
),
);
}
}
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),
),
);
}
}
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.
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
constconstructors: Useconstfor widgets that never change to improve performance - Modifying state without
setState(): The UI will not update unless you wrap changes insetState() - 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';
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, andFloatingActionButton - Work with
StatefulWidgetandsetState() - 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.