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
- Add saving to the to-do list with shared_preferences.
- Load real data from the internet with HTTP requests.
- Pick a bigger idea from Flutter project ideas.
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.