Your First App
Time to create a real Flutter project and understand exactly what Flutter generates for you. Every new project starts from the same scaffolded "counter app" — a small but genuinely complete example that touches widgets, state, and layout all at once. Understanding it line by line here will make every later lesson land faster.
Creating a New Project
flutter create my_app
cd my_app
This scaffolds a full project directory. Here's what matters at a glance:
| Path | What it is |
|---|---|
lib/main.dart | Where your Dart code lives and where the app starts running. This is the file you'll edit the most, by far. |
pubspec.yaml | The project's manifest — name, dependencies, assets. Covered in full in the next lesson. |
android/ | The native Android wrapper project. Generated for you; you rarely need to open it unless configuring platform-specific settings like app icons or permissions. |
ios/ | The native iOS wrapper project (Xcode project files). Same idea as android/, rarely touched directly. |
test/ | Where automated tests live — covered later in the Testing lesson. |
lib/, writing Dart. The native folders exist so flutter run can build a real Android/iOS app around your Dart code, but day-to-day Flutter development rarely requires touching them.The Entry Point: main() and runApp()
Every Dart program starts executing at a top-level function named main(). In a
Flutter app, main()'s job is simply to call runApp(), handing it the
root widget of your entire application.
void main() {
runApp(const MyApp());
}
runApp() takes that widget, attaches it to the screen, and hands control over to the
Flutter framework, which takes it from there — rendering, handling input, and rebuilding widgets
as needed for the lifetime of the app.
Walking Through the Generated Counter App
Here is a trimmed-down version of what flutter create generates in
lib/main.dart, with each piece explained below it.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
MyApp is the root widget, and it's a StatelessWidget — it has no
internal state of its own to manage. Its only job is to return a MaterialApp, which
sets up app-wide concerns (title, theme, and the starting screen) and provides Material Design
behavior throughout. The home: parameter points at the actual first screen,
MyHomePage.
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(title: Text(widget.title)),
body: Center(
child: Text('Count: $_counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: const Icon(Icons.add),
),
);
}
}
MyHomePage is a StatefulWidget — unlike MyApp, it has data
that changes over time (the counter), so it needs somewhere to hold that mutable state. That's
what _MyHomePageState is for: a matching State object that actually
holds _counter and rebuilds the UI whenever it changes.
_counter— a private field (the leading underscore makes it private to this file) holding the current count._incrementCounter()— increments the counter, but crucially wraps the change insetState(() { ... }). CallingsetStateis how you tell Flutter "state changed, please rebuild this widget" — without it,_counterwould change internally but the screen would never update.build()— describes the UI as a tree of widgets: aScaffold(the basic page structure), anAppBar(top title bar), a centeredTextshowing the count, and aFloatingActionButtonwhoseonPressedcalls_incrementCounter.
StatelessWidget vs StatefulWidget and exactly how setState works under the hood get their own dedicated lessons later in this track — for now, just recognize the shape of the pattern.Running the App
flutter run
With a device or emulator connected, this builds the app and installs it, then keeps a live session open in your terminal. That session listens for single-key commands while the app is running.
Hot Reload: Flutter's Standout Feature
While flutter run is active, saving a change to your Dart code and pressing a single
key updates the running app almost instantly — typically well under a second —
without restarting the app or losing its current state. If a form had text typed into it, or the
counter was at 7, it stays that way after a hot reload; only your code changes are applied.
| Key | Action |
|---|---|
r | Hot reload — injects updated source code into the running app and rebuilds the widget tree, preserving state |
R | Hot restart — fully restarts the app from scratch, losing state, but re-runs main(). Needed for changes hot reload can't handle, like changes to main() itself or to native code |
q | Quit the running session |
my_app project to actually see it.Worked Example: See Hot Reload In Action
With flutter run still active from earlier, open lib/main.dart and make
two small changes to the counter app: give the button a different color, and change the starting
label text.
// Before
body: Center(
child: Text('Count: $_counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: const Icon(Icons.add),
),
// After -- new label text and a custom button color
body: Center(
child: Text('Taps so far: $_counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
backgroundColor: Colors.deepPurple,
child: const Icon(Icons.add),
),
Save the file, then press r in the terminal running flutter run. The
label text and button color update in place within a second — and if you had already tapped the
button a few times before saving, the counter's current value is preserved rather than reset to
zero, because hot reload only re-applies your code changes on top of the existing running state.