Flutter Development: A Comprehensive Introduction
Flutter Overview
Flutter is an open-source UI framework created by Google for building natively compiled applications for mobile, web, and desktop from a single codebase. It uses the Dart language and provides a rich set of pre-designed widgets. Flutter's own rendering engine draws every pixel on the screen, offering high performance and a consistent look across platforms.
Key features:
- Cross-platform dveelopment with a single codebase
- High-performance randering via a custom engine
- Extensive widget library with material design and Cupertino styles
Environment Setup
- Download the Flutter SDK from the official archive or GitHub releases.
- Extract the archive and add the
flutter/bindirectory to your system PATH. - Run
flutter doctorto verify that all dependencies are installed.
Create your first project:
- Install the Flutter plugin for Android Studio.
- Use Android Studio to create a new Flutter project.
Dart SDK Setup
- Download the Dart SDK from the official website or the Windows archive.
Dart Language Basics
Variables and Constants
void main() {
var username = "Alice";
username = "Bob";
dynamic temp = "test";
Object obj = "test";
final password = "secret";
// password = "newsecret"; // Error
const apiKey = "12345";
// apiKey = "67890"; // Error
}
Data Types
void main() {
// Numbers
int a = 10;
int parsedInt = int.parse('42');
double b = 3.14;
double parsedDouble = double.parse('2.718');
// Strings
String fromInt = 123.toString();
String formatted = 3.14159.toStringAsFixed(2);
// Booleans
bool compare = 1 == '1'; // false
bool greater = 5 > 3; // true
// Lists
List<int> numbers = [1, 3, 5, 7, 9];
// List<int> more = List(); // Deprecated
// more.add(2);
// more.addAll([4, 6, 8, 10]);
// Maps
Map<String, int> point = {'x': 1, 'y': 2};
// Map<String, int> other = Map(); // Deprecated
// other['x'] = 1;
print(point.containsKey('x')); // true
point.remove('y');
print(point); // {x: 1}
}
Functions
void main() {
print(getUserName());
print(getPersonInfo(111));
print(addAge(10));
print(addAge2(age1: 20, age2: 5));
var items = ["alpha", "beta", "gamma"];
items.forEach((item) {
print(item);
});
}
String getUserName() => "Hello World";
String getPersonInfo(int userId) {
Map<int, String> users = {111: "Alice", 222: "Bob"};
return users[userId] ?? "Unknown";
}
int addAge(int age1, [int age2 = 0]) => age1 + age2;
int addAge2({int age1 = 0, int age2 = 0}) => age1 + age2;
Classes and Inheritance
void main() {
var person = Person(25, "Charlie");
print(person.age);
person.sayHello();
var worker = Worker(30, "Dave", 5000);
worker.sayHello();
}
class Person {
int age;
String name;
Person(this.age, this.name);
void sayHello() {
print("My name is $name");
}
}
class Worker extends Person {
int salary;
Worker(int age, String name, this.salary) : super(age, name);
@override
void sayHello() {
super.sayHello();
print("My salary is $salary");
}
}
Mixins and Abstract Classes
Dart does not support multiple inheritance. Mixins allow code reuse across class hierarchies.
void main() {
var person = Person(20, "Eve");
person.eat();
person.sleep();
person.haveABaby();
}
mixin Eat {
void eat() => print("Eating...");
}
mixin Sleep {
void sleep() => print("Sleeping...");
}
abstract class Animal {
void haveABaby();
}
class Person extends Animal with Eat, Sleep {
int age;
String name;
Person(this.age, this.name);
void sayHello() => print("Hi, I'm $name");
@override
void haveABaby() => print("Having a baby");
}
Libraries and Packages
import 'pkg/Calculator.dart';
import 'package:http/http.dart' as http;
import 'dart:math' deferred as math;
void main() async {
var calc = Calculator(10, 5);
calc.subtract();
await math.loadLibrary();
var random = math.Random();
print(random.nextInt(10));
}
Asynchronous Programming
void main() async {
print("Start");
await Future.delayed(Duration(seconds: 2), () => print("Async task"));
print("End");
// Wait for multiple futures
await Future.wait([
Future.delayed(Duration(seconds: 1), () => print("First")),
Future.delayed(Duration(seconds: 3), () => print("Second")),
Future.delayed(Duration(seconds: 2), () => print("Third")),
]);
print("All done");
}
Widgets
Text and Styling
@override
Widget build(BuildContext context) {
const customStyle = TextStyle(
color: Color(0xFFFF0000),
fontSize: 20,
fontFamily: 'Roboto',
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.dashed,
);
return Scaffold(
appBar: AppBar(title: Text('Text Demo')),
body: Column(
children: [
Text(
'Long text that will be truncated with ellipsis',
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
textScaleFactor: 1.5,
),
Text('Styled text', style: customStyle),
Text.rich(
TextSpan(
children: [
TextSpan(text: 'Visit ', style: TextStyle(fontSize: 18)),
TextSpan(
text: 'our website',
style: TextStyle(color: Colors.blue, fontSize: 20),
),
],
),
),
],
),
);
}
Buttons
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Buttons')),
body: Column(
children: [
ElevatedButton(
onPressed: () => print('Elevated pressed'),
child: Text('Elevated'),
),
TextButton(
onPressed: () => print('Text pressed'),
child: Text('Text'),
),
OutlinedButton(
onPressed: () => print('Outlined pressed'),
child: Text('Outlined'),
),
TextButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
child: Text('Custom'),
),
],
),
);
}
Images and Icons
Local Assets
Image.asset('assets/images/logo.png', width: 200)
Network Images
Image.network(
'https://example.com/image.png',
width: 150,
fit: BoxFit.cover,
color: Colors.pink,
colorBlendMode: BlendMode.difference,
)
Custom Icon Font
- Add the font file to your project's assets and declare it in
pubspec.yaml. - Create a custom icon class:
import 'package:flutter/material.dart';
class MyIcons {
static const IconData home = IconData(0xe900, fontFamily: 'MyCustomIcons');
static const IconData settings = IconData(0xe901, fontFamily: 'MyCustomIcons');
}
- Use it:
Icon(MyIcons.home, color: Colors.amber);
Dropdown Button
class CitySelector extends StatefulWidget {
@override
_CitySelectorState createState() => _CitySelectorState();
}
class _CitySelectorState extends State<CitySelector> {
String? _selectedCity;
List<DropdownMenuItem<String>> _buildCityMenu() {
return ['Shanghai', 'Beijing', 'Guangzhou', 'Shenzhen']
.map((city) => DropdownMenuItem(value: city, child: Text(city)))
.toList();
}
@override
Widget build(BuildContext context) {
return DropdownButton<String>(
items: _buildCityMenu(),
hint: Text('Select a city'),
value: _selectedCity,
onChanged: (value) {
setState(() => _selectedCity = value);
},
);
}
}
Radio and Checkbox
Single Seelction (Radio)
class FruitSelector extends StatefulWidget {
@override
_FruitSelectorState createState() => _FruitSelectorState();
}
class _FruitSelectorState extends State<FruitSelector> {
String? _selectedFruit;
@override
Widget build(BuildContext context) {
return Column(
children: [
RadioListTile<String>(
title: Text('Apple'),
value: 'apple',
groupValue: _selectedFruit,
onChanged: (value) => setState(() => _selectedFruit = value),
),
RadioListTile<String>(
title: Text('Banana'),
value: 'banana',
groupValue: _selectedFruit,
onChanged: (value) => setState(() => _selectedFruit = value),
),
],
);
}
}
Multiple Selection (Checkbox)
class HobbySelector extends StatefulWidget {
@override
_HobbySelectorState createState() => _HobbySelectorState();
}
class _HobbySelectorState extends State<HobbySelector> {
List<String> _selectedHobbies = [];
@override
Widget build(BuildContext context) {
return Column(
children: [
CheckboxListTile(
title: Text('Reading'),
value: _selectedHobbies.contains('reading'),
onChanged: (value) {
setState(() {
value
? _selectedHobbies.add('reading')
: _selectedHobbies.remove('reading');
});
},
),
CheckboxListTile(
title: Text('Coding'),
value: _selectedHobbies.contains('coding'),
onChanged: (value) {
setState(() {
value
? _selectedHobbies.add('coding')
: _selectedHobbies.remove('coding');
});
},
),
],
);
}
}
Text Input
class TextInputExample extends StatefulWidget {
@override
_TextInputExampleState createState() => _TextInputExampleState();
}
class _TextInputExampleState extends State<TextInputExample> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16),
child: TextField(
controller: _controller,
decoration: InputDecoration(
labelText: 'Type something',
border: OutlineInputBorder(),
),
),
);
}
}
Layouts
Linear Layout (Row & Column)
// Row
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(width: 100, height: 200, color: Colors.red),
Container(width: 100, height: 300, color: Colors.blue),
],
)
// Column
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(width: 100, height: 200, color: Colors.red),
Container(width: 100, height: 300, color: Colors.blue),
],
)
Flex Layout
Flex(
direction: Axis.horizontal,
children: [
Container(width: 50, height: 300, color: Colors.black),
Expanded(flex: 1, child: Container(height: 300, color: Colors.red)),
Expanded(flex: 1, child: Container(height: 300, color: Colors.blue)),
Expanded(flex: 2, child: Container(height: 300, color: Colors.yellow)),
],
)
Stack and Positioned
SizedBox(
height: 400,
width: 400,
child: Stack(
alignment: Alignment.topRight,
children: [
Positioned(
left: 15,
top: 30,
child: Container(width: 100, height: 100, color: Colors.red),
),
Positioned(
right: 50,
top: 80,
child: Container(width: 200, height: 200, color: Colors.blue),
),
],
),
)
Wrap Layout
Wrap(
children: [
Container(width: 150, height: 150, color: Colors.red),
Container(width: 150, height: 150, color: Colors.blue),
Container(width: 150, height: 150, color: Colors.green),
],
)
Containers
Padding
Container(
width: 400,
height: 400,
color: Colors.red,
child: Padding(
padding: EdgeInsets.only(top: 100),
child: Container(color: Colors.blue),
),
)
ConstrainedBox
ConstrainedBox(
constraints: BoxConstraints(
minWidth: double.infinity,
minHeight: 200,
maxHeight: 300,
),
child: Container(width: 1, color: Colors.red),
)
DecoratedBox
DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [Colors.yellow, Colors.red]),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(color: Colors.black, offset: Offset(3, 3), blurRadius: 4),
],
),
child: TextButton(onPressed: () {}, child: Text('Styled Button')),
)
Transform
// Translation
Transform.translate(
offset: Offset(50, 10),
child: Text('Moved text'),
)
// Rotation
Transform.rotate(
angle: pi / 2,
child: Text('Rotated text'),
)
// Scale
Transform.scale(
scale: 2,
child: Text('Scaled text'),
)
Container
Column(
children: [
Container(
margin: EdgeInsets.only(bottom: 20),
color: Colors.blue,
height: 200,
width: 300,
alignment: Alignment.centerRight,
child: Text('Box 1'),
),
Container(
margin: EdgeInsets.only(top: 20),
color: Colors.red,
height: 200,
width: 300,
alignment: Alignment.centerRight,
child: Text('Box 2'),
),
],
)
Scrollable Widgets
// SingleChildScrollView
Scrollbar(
child: SingleChildScrollView(
child: Container(height: 3000, color: Colors.red),
),
)
// ListView.builder
Container(
height: 400,
child: ListView.builder(
itemCount: 50,
itemExtent: 50,
itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
),
)
// GridView
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
childAspectRatio: 1,
),
children: List.generate(9, (i) => Center(child: Text('${i+1}'))),
)
Scaffold Structure
AppBar and Bottom Navigation
class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 1;
final List<Widget> _pages = [PageOne(), PageTwo(), PageThree()];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main App'),
actions: [
IconButton(
icon: Icon(Icons.settings),
onPressed: () => print('Settings pressed'),
),
],
),
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => print('FAB pressed'),
),
);
}
}
TabBar
class TabbedPage extends StatefulWidget {
@override
_TabbedPageState createState() => _TabbedPageState();
}
class _TabbedPageState extends State<TabbedPage> with SingleTickerProviderStateMixin {
final List<String> _tabs = ['Tab A', 'Tab B', 'Tab C'];
late TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(length: _tabs.length, vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
color: Colors.pink,
child: TabBar(
controller: _controller,
tabs: _tabs.map((t) => Tab(text: t)).toList(),
),
),
Expanded(
child: TabBarView(
controller: _controller,
children: _tabs.map((t) => Center(child: Text('$t content'))).toList(),
),
),
],
);
}
}
Drawer
Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Drawer Demo'),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
),
),
drawer: Drawer(
child: Container(
width: 300,
color: Colors.pink,
child: Column(
children: [Text('Drawer content')],
),
),
),
body: Center(child: Text('Main content')),
)
Events
Pointer Events
Listener(
onPointerDown: (event) => print('Down'),
onPointerUp: (event) => print('Up'),
onPointerMove: (event) => print('Move'),
onPointerCancel: (event) => print('Cancel'),
child: Container(height: 200, width: 200, color: Colors.red),
)
Gesture Events
GestureDetector(
onTap: () => print('Tap'),
onDoubleTap: () => print('Double tap'),
onLongPress: () => print('Long press'),
onHorizontalDragStart: (details) => print('Horizontal drag start'),
onScaleUpdate: (details) => print('Scale update'),
child: Container(width: 200, height: 200, color: Colors.blue),
)
HTTP Requests
import 'package:dio/dio.dart';
class NetworkPage extends StatefulWidget {
@override
_NetworkPageState createState() => _NetworkPageState();
}
class _NetworkPageState extends State<NetworkPage> {
String _responseData = "";
Future<void> fetchData() async {
try {
var response = await Dio().get(
'https://api.example.com/data',
queryParameters: {'key': 'value'},
);
setState(() => _responseData = response.data.toString());
} catch (e) {
print('Error: $e');
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: fetchData,
child: Text('Fetch Data'),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
),
Scrollbar(
child: Container(
height: 400,
child: SingleChildScrollView(
child: Text(_responseData),
),
),
),
],
);
}
}