Lesson 1

Lesson 1. Your first Flutter app: Hello World

Egor Dolgov · 09.11.2026
How shall we create the project?
>_
Terminal
{ }
VS Code
AS
Android Studio
Start the interactive lesson
Subtitles
Let's set up the lesson for you: your system, how you create the project, and where you run it.

Let's write a first app from scratch: show “Hello World!” on a screen with a header, move it into a widget of our own and open a website at the press of a button. On the way we will see where every Flutter program begins.

Before the lesson you need Flutter installed and an editor set up — that is Lesson 0. Installing Flutter and setting up your editor . You can run the example anywhere: in Chrome, as a desktop app, in an emulator or on a phone.

Contents:

  1. Create and run a project
  2. The main() function
  3. The runApp() function and the first screen
  4. A widget of your own
  5. Hot reload
  6. Your first package: opening a link
  7. Summary

1. Create and run a project

The flutter create command makes a new project. The project name is in Latin letters, lowercase, with words separated by underscores. The --empty flag creates a minimal project — it is the easiest place to start:

flutter create --empty hello_flutter

At the end the command tells you what to do next:

In order to run your empty application, type:

  $ cd hello_flutter
  $ flutter run

Your empty application code is in hello_flutter/lib/main.dart.

Run those two commands: go into the project folder and start it. If “Hello World!” appears on the screen, Flutter works and you can start writing code.

In VS Code the same project is created with the Flutter: Create New Project command from the command palette and the Empty Application template; in Android Studio, with the New Flutter Project wizard and the Empty Project type.

2. The main() function

Open lib/main.dart — Flutter runs this file by default. Find the line void main() in it: execution of every Dart program — and every Flutter app — starts with the main() function.

To understand every line, we will write the program ourselves. Delete everything in the file and start with an empty function:

void main() {
}

void means the function returns nothing. An interface appears only when main() hands Flutter a widget to draw — that is what runApp() does.

3. The runApp() function and the first screen

To show an interface, main() calls runApp() and passes it a widget. A widget describes a piece of the interface: a text, a button, a gap or a whole screen. A Flutter interface is built from widgets nested inside each other. Replace the code in lib/main.dart :

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('flutter.su')),
        body: const Center(child: Text('Hello World!')),
      ),
    ),
  );
}
  • runApp() takes a widget and stretches it over the whole app window.
  • MaterialApp is the root of the app: it sets up the theme and navigation. Scaffold is the frame of a screen, with room for a header ( appBar ) and content ( body ). More about them in separate lessons.
  • Center puts its child ( child ) in the middle, Text shows a line of text.
  • import 'package:flutter/material.dart' brings in Flutter's widgets, Material Design widgets included.

Run the app with flutter run or the run button in your editor: you will see a “flutter.su” header and “Hello World!” in the middle of the screen.

4. A widget of your own

When a screen grows, its parts are moved into widgets of their own. The simplest kind is a class that extends StatelessWidget : it has no mutable state and describes the interface in its build() method. Replace the code:

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(
      home: Scaffold(
        appBar: AppBar(title: const Text('flutter.su')),
        body: const MyBody(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const Center(child: Text('Hello World!'));
  }
}
  • MyApp is the whole app, MyBody is the content of the screen.
  • const MyApp({super.key}) is the constructor; key is passed on to the parent class.
  • build() returns the widget tree Flutter will draw.

Nothing changed on the screen — only the way the code is organised.

5. Hot reload

Change the text in MyBody and save the file — without restarting the app. This is hot reload : Flutter puts the new code into the running app and keeps its state. If the app was started from a terminal, the r key triggers a reload — flutter run prints the hint itself:

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

Hot reload will not pick up changes to the main() function itself — those need a hot restart (the R key): the app starts over and loses its state.

Packages from pub.dev extend what Flutter can do. Let's add a button that opens a website — the url_launcher package does that. Add it with a command run in the project folder:

flutter pub add url_launcher

The command does two things: it adds the package to the pubspec.yaml file in the project root and downloads it (the same as flutter pub get ). Open pubspec.yaml — a url_launcher line has appeared under dependencies :

dependencies:
  flutter:
    sdk: flutter
  url_launcher: ^6.3.3

^6.3.3 means “any compatible version”: 6.3.3 or later, but below 7.0.0.

pubspec.yaml is an ordinary text file and you can edit it yourself: add the package under dependencies and run flutter pub get . VS Code has a Get Packages button above an open pubspec.yaml for that, Android Studio has Pub get .

url_launcher has code for each platform (Android, iOS, web…). Hot reload does not pick up such code: after adding the package, stop the app and start it again, otherwise you may get a MissingPluginException error.

Now the complete lib/main.dart :

import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('flutter.su')),
        body: const MyBody(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text('Hello World!'),
          const SizedBox(height: 16),
          FilledButton(
            onPressed: _openSite,
            child: const Text('Open flutter.su'),
          ),
        ],
      ),
    );
  }
}

Future<void> _openSite() async {
  final url = Uri.parse('https://flutter.su');
  if (!await launchUrl(url)) {
    throw Exception('Could not open $url');
  }
}
  • Column places widgets one under another; mainAxisSize: MainAxisSize.min shrinks the column to its content so that Center can centre it.
  • SizedBox(height: 16) is empty space between the text and the button.
  • FilledButton is a filled Material 3 button; onPressed is called when it is pressed.
  • launchUrl() opens the address. If that fails, the function returns false or throws a PlatformException , depending on the reason.
Earlier versions of this lesson used FlatButton , canLaunch() and launch() . There is no FlatButton class in modern Flutter — it was replaced by TextButton ( migration guide ). In url_launcher, canLaunch() and launch() have been deprecated since version 6.1.0; use canLaunchUrl() and launchUrl() , which take a Uri , instead.

7. Summary

  • flutter create --empty creates a project, flutter run starts it.
  • A program starts with main() , and runApp() shows the interface.
  • An interface is built from nested widgets; the root of the app is MaterialApp , the frame of a screen is Scaffold .
  • Your own widgets extend StatelessWidget and describe the interface in build() .
  • Hot reload ( r ) changes code without losing state, hot restart ( R ) starts the app over.
  • Packages are added with flutter pub add — it writes them into pubspec.yaml ; you can also edit that file by hand and then run flutter pub get .

Next: Lesson 2. StatelessWidget and StatefulWidget (in Russian for now).

Send questions and suggestions about this lesson to contact@flutter.su.