Flutter is Google's toolkit to build mobile, web, and desktop apps using a single codebase in Dart language. Think of it like a "Lego set" — you assemble small blocks called widgets to make full apps.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Hello Flutter')),
body: Center(
child: Text('Welcome to Flutter!'),
),
),
);
}
}
1. main() runs first and calls runApp().
2. Flutter mounts MyApp as the root widget.
3. MaterialApp provides default styling.
4. Scaffold builds the basic page layout.
5. Engine draws pixels on Android/iOS screen.
Change the text inside Text('Welcome to Flutter!') to your name and add an emoji.
Android Studio is the official IDE for Flutter. You install Flutter SDK + Android Studio + Flutter plugin to start coding.
flutter doctor
flutter create my_first_app
cd my_first_app
flutter run
Goal: Build a simple greeting card app.
import 'package:flutter/material.dart';
void main() => runApp(MyCardApp());
class MyCardApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.teal[50],
appBar: AppBar(title: Text('My First Card')),
body: Center(
child: Container(
padding: EdgeInsets.all(20),
color: Colors.white,
child: Text('Hi, I am a Flutter Developer!',
style: TextStyle(fontSize: 20)),
),
),
),
);
}
}
A Widget is the smallest building block of Flutter UI — like a LEGO brick. Buttons, text, padding, even the whole screen is a widget.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Widget Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('I am a Text Widget'),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {},
child: Text('I am a Button Widget'),
),
],
),
),
),
);
}
}
Stateless = Static screen (doesn't change). Like a printed photo.
Stateful = Dynamic screen (can change with user actions). Like a digital clock.
import 'package:flutter/material.dart';
void main() => runApp(CounterApp());
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
class _CounterAppState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Stateful Demo')),
body: Center(
child: Text('Count: $count',
style: TextStyle(fontSize: 30)),
),
floatingActionButton: FloatingActionButton(
onPressed: () { setState(() { count++; }); },
child: Icon(Icons.add),
),
),
);
}
}
Row arranges children horizontally (left to right). Column arranges children vertically (top to bottom). Container is a box you can style with color, padding, margin, and borders.
import 'package:flutter/material.dart';
void main() => runApp(LayoutDemo());
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Layout Demo')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(color: Colors.red, height: 60, width: 200),
Container(color: Colors.green, height: 60, width: 200),
Container(color: Colors.blue, height: 60, width: 200),
],
),
),
);
}
}
1. Column takes a list of children.
2. Each Container gets a color, height, and width.
3. mainAxisAlignment: center centers them vertically.
4. Flutter measures each child, then positions them top-to-bottom.
Change Column to Row and change mainAxisAlignment to spaceEvenly. Observe the difference.
Padding adds space inside a widget. Expanded makes a child fill remaining space in Row/Column. Stack layers widgets on top of each other like a deck of cards.
import 'package:flutter/material.dart';
void main() => runApp(StackDemo());
class StackDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Stack Demo')),
body: Center(
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 200, height: 200, color: Colors.red),
Positioned(
top: 30, left: 30,
child: Container(
width: 140, height: 140,
color: Colors.green)),
Positioned(
top: 60, left: 60,
child: Container(
width: 80, height: 80,
color: Colors.blue)),
],
),
),
),
);
}
}
Goal: Build a profile card using Column, Container, Padding, and Stack.
import 'package:flutter/material.dart';
void main() => runApp(ProfileCardApp());
class ProfileCardApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.grey[200],
body: Center(
child: Container(
width: 300,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(blurRadius: 10, color: Colors.grey)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 120,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20)),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
CircleAvatar(radius: 40,
backgroundColor: Colors.blue),
SizedBox(height: 12),
Text('Jane Doe',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold)),
Text('Flutter Developer',
style: TextStyle(
color: Colors.grey)),
],
),
),
],
),
),
),
),
);
}
}
Flutter uses a Navigator stack to manage screens. Navigator.push() puts a new screen on top. Navigator.pop() removes the top screen and goes back. It works exactly like a browser's back button.
import 'package:flutter/material.dart';
void main() => runApp(NavDemo());
class NavDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FirstScreen(),
);
}
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First Screen')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => SecondScreen()),
);
},
child: Text('Go to Second Screen'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second Screen')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go Back'),
),
),
);
}
}
1. FirstScreen is the initial route.
2. Tapping the button calls Navigator.push() with a MaterialPageRoute.
3. SecondScreen slides in from the right and is placed on the stack.
4. Tapping "Go Back" calls Navigator.pop(), removing SecondScreen.
5. FirstScreen becomes visible again with a slide-left animation.
Add a third screen and create a chain: First → Second → Third → pop back to Second → pop back to First.
Named routes let you define all screen paths in one place (like a map). Passing data means sending information (like a name or ID) from one screen to another through the route arguments.
import 'package:flutter/material.dart';
void main() => runApp(NamedRouteApp());
class NamedRouteApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (ctx) => HomeScreen(),
'/detail': (ctx) {
final args = ModalRoute.of(ctx)
?.settings.arguments as Map?;
return DetailScreen(
name: args?['name'] ?? 'Unknown',
age: args?['age'] ?? 0,
);
},
},
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/detail',
arguments: {'name': 'Jane', 'age': 25});
},
child: Text('View Details'),
),
),
);
}
}
class DetailScreen extends StatelessWidget {
final String name;
final int age;
DetailScreen({required this.name, required this.age});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Name: $name', style: TextStyle(fontSize: 24)),
Text('Age: $age', style: TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text('Go Back'),
),
],
),
),
);
}
}
Goal: Build a contact list app with 3 screens: Contact List, Contact Detail, and Settings, using named routes with data passing.
import 'package:flutter/material.dart';
void main() => runApp(ContactApp());
class ContactApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (ctx) => ContactListScreen(),
'/detail': (ctx) {
final args =
ModalRoute.of(ctx)?.settings.arguments as Map;
return ContactDetailScreen(
name: args['name'], phone: args['phone']);
},
'/settings': (ctx) => SettingsScreen(),
},
);
}
}
class ContactListScreen extends StatelessWidget {
final contacts = [
{'name': 'Alice', 'phone': '555-0101'},
{'name': 'Bob', 'phone': '555-0202'},
{'name': 'Charlie', 'phone': '555-0303'},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Contacts'),
actions: [
IconButton(
icon: Icon(Icons.settings),
onPressed: () =>
Navigator.pushNamed(context, '/settings'),
),
],
),
body: ListView.builder(
itemCount: contacts.length,
itemBuilder: (ctx, i) => ListTile(
leading: CircleAvatar(child: Text(contacts[i]['name'][0])),
title: Text(contacts[i]['name']),
subtitle: Text(contacts[i]['phone']),
onTap: () => Navigator.pushNamed(
context, '/detail', arguments: contacts[i]),
),
),
);
}
}
class ContactDetailScreen extends StatelessWidget {
final String name, phone;
ContactDetailScreen({required this.name, required this.phone});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(name)),
body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(radius: 50,
child: Text(name[0], style: TextStyle(fontSize: 40))),
SizedBox(height: 20),
Text(name, style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
Text(phone, style: TextStyle(fontSize: 20, color: Colors.grey)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text('Go Back')),
],
),
),
);
}
}
class SettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Settings')),
body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Settings', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text('Go Back')),
],
),
),
);
}
}
Step through code execution line-by-line. Watch variables change and see output in real-time.