Flutter Tutorial

Flutter Advanced Lesson 132 of 135 6 min read

Flutter Examples for Beginners: 8 Complete Apps You Can Run

Easy Flutter examples for beginners with full code: hello app, counter, ListView, TextField form, profile card, navigation, dark mode switch and a to-do list.

On this page

Each example below is a complete Flutter app in one file. Press Run to see it working in the browser, then edit the code and run it again. No install needed. When you are ready to run them on a phone, copy the code into lib/main.dart of a new project (see Create and run your first Flutter app).

The examples get a little harder as you go. Every widget used here has its own lesson in the Flutter course.

1. Hello Flutter

The smallest useful app: a screen with a title bar and centred text.

import 'package:flutter/material.dart';

void main() => runApp(const HelloApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(colorSchemeSeed: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: const Text('My first app')),
        body: const Center(
          child: Text('Hello, Flutter!', style: TextStyle(fontSize: 28)),
        ),
      ),
    );
  }
}

Learn more: StatelessWidget, Scaffold and AppBar.

2. Counter app

A number that goes up and down. This is how setState works.

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: CounterPage()));

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Text('$_count', style: const TextStyle(fontSize: 64)),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'minus',
            onPressed: () => setState(() => _count--),
            child: const Icon(Icons.remove),
          ),
          const SizedBox(width: 12),
          FloatingActionButton(
            heroTag: 'plus',
            onPressed: () => setState(() => _count++),
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

Learn more: StatefulWidget and setState.

3. List of items with ListView.builder

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: CountriesPage()));

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

  static const countries = [
    ('Nepal', 'Kathmandu'),
    ('India', 'New Delhi'),
    ('United States', 'Washington, D.C.'),
    ('Canada', 'Ottawa'),
    ('Australia', 'Canberra'),
    ('Portugal', 'Lisbon'),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Countries')),
      body: ListView.builder(
        itemCount: countries.length,
        itemBuilder: (context, index) {
          final (country, capital) = countries[index];
          return ListTile(
            leading: CircleAvatar(child: Text(country[0])),
            title: Text(country),
            subtitle: Text('Capital: $capital'),
            onTap: () => ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('You tapped $country')),
            ),
          );
        },
      ),
    );
  }
}

Learn more: ListView, Cards and ListTiles.

4. Text input and a button

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: GreetPage()));

class GreetPage extends StatefulWidget {
  const GreetPage({super.key});

  @override
  State<GreetPage> createState() => _GreetPageState();
}

class _GreetPageState extends State<GreetPage> {
  final _controller = TextEditingController();
  String _message = 'Type your name';

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _greet() {
    final name = _controller.text.trim();
    setState(() {
      _message = name.isEmpty ? 'Please enter a name' : 'Namaste, $name!';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Greeter')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            TextField(
              controller: _controller,
              decoration: const InputDecoration(
                labelText: 'Your name',
                border: OutlineInputBorder(),
              ),
              onSubmitted: (_) => _greet(),
            ),
            const SizedBox(height: 16),
            FilledButton(onPressed: _greet, child: const Text('Say hello')),
            const SizedBox(height: 24),
            Text(_message, style: Theme.of(context).textTheme.headlineSmall),
          ],
        ),
      ),
    );
  }
}

Learn more: TextField, Buttons.

5. Profile card with Row and Column

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: ProfilePage()));

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blueGrey.shade50,
      body: Center(
        child: Card(
          margin: const EdgeInsets.all(24),
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const CircleAvatar(radius: 40, child: Icon(Icons.person, size: 40)),
                const SizedBox(height: 12),
                const Text('Asha Gurung',
                    style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
                const Text('Flutter developer, Pokhara'),
                const SizedBox(height: 16),
                Row(
                  mainAxisSize: MainAxisSize.min,
                  children: const [
                    _Stat(label: 'Apps', value: '12'),
                    SizedBox(width: 24),
                    _Stat(label: 'Stars', value: '340'),
                    SizedBox(width: 24),
                    _Stat(label: 'Years', value: '3'),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _Stat extends StatelessWidget {
  const _Stat({required this.label, required this.value});
  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(value, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
        Text(label),
      ],
    );
  }
}

Learn more: Row and Column, Padding and alignment.

6. Move between two screens

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: HomePage()));

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: FilledButton(
          child: const Text('Open details'),
          onPressed: () async {
            final reply = await Navigator.push<String>(
              context,
              MaterialPageRoute(builder: (_) => const DetailsPage(item: 'Dart book')),
            );
            if (reply != null && context.mounted) {
              ScaffoldMessenger.of(context)
                  .showSnackBar(SnackBar(content: Text(reply)));
            }
          },
        ),
      ),
    );
  }
}

class DetailsPage extends StatelessWidget {
  const DetailsPage({super.key, required this.item});
  final String item;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(item)),
      body: Center(
        child: FilledButton(
          onPressed: () => Navigator.pop(context, 'You liked $item'),
          child: const Text('Like and go back'),
        ),
      ),
    );
  }
}

Learn more: Navigator push and pop, Passing data between screens.

7. Light and dark mode switch

import 'package:flutter/material.dart';

void main() => runApp(const ThemeDemoApp());

class ThemeDemoApp extends StatefulWidget {
  const ThemeDemoApp({super.key});

  @override
  State<ThemeDemoApp> createState() => _ThemeDemoAppState();
}

class _ThemeDemoAppState extends State<ThemeDemoApp> {
  bool _dark = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(colorSchemeSeed: Colors.teal),
      darkTheme: ThemeData(colorSchemeSeed: Colors.teal, brightness: Brightness.dark),
      themeMode: _dark ? ThemeMode.dark : ThemeMode.light,
      home: Scaffold(
        appBar: AppBar(title: const Text('Theme switch')),
        body: Center(
          child: SwitchListTile(
            title: const Text('Dark mode'),
            secondary: Icon(_dark ? Icons.dark_mode : Icons.light_mode),
            value: _dark,
            onChanged: (value) => setState(() => _dark = value),
          ),
        ),
      ),
    );
  }
}

Learn more: Themes and dark mode, Checkbox, Switch and Slider.

8. To-do list

Add tasks, tick them off, and swipe to delete. This puts most of the earlier ideas together.

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: TodoPage()));

class Todo {
  Todo(this.title);
  final String title;
  bool done = false;
}

class TodoPage extends StatefulWidget {
  const TodoPage({super.key});

  @override
  State<TodoPage> createState() => _TodoPageState();
}

class _TodoPageState extends State<TodoPage> {
  final _todos = [Todo('Learn Dart basics'), Todo('Build a Flutter app')];
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _add() {
    final text = _controller.text.trim();
    if (text.isEmpty) return;
    setState(() => _todos.add(Todo(text)));
    _controller.clear();
  }

  @override
  Widget build(BuildContext context) {
    final left = _todos.where((t) => !t.done).length;
    return Scaffold(
      appBar: AppBar(title: Text('To-do ($left left)')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(hintText: 'New task'),
                    onSubmitted: (_) => _add(),
                  ),
                ),
                IconButton(onPressed: _add, icon: const Icon(Icons.add)),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _todos.length,
              itemBuilder: (context, i) {
                final todo = _todos[i];
                return Dismissible(
                  key: ObjectKey(todo),
                  onDismissed: (_) => setState(() => _todos.removeAt(i)),
                  background: Container(color: Colors.red.shade200),
                  child: CheckboxListTile(
                    title: Text(
                      todo.title,
                      style: TextStyle(
                        decoration: todo.done ? TextDecoration.lineThrough : null,
                      ),
                    ),
                    value: todo.done,
                    onChanged: (v) => setState(() => todo.done = v ?? false),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

Learn more: Gestures and Dismissible, Keys in Flutter, State in Flutter.

What to build next

Try it yourself

Change the counter app so the number turns red below zero and green above zero, and add a reset button in the app bar.

Practise in the playground Updated