Flutter Development
Build beautiful, natively compiled applications for mobile, web, desktop, and embedded from a single codebase.
Supported Platforms
| Platform |
Status |
Notes |
| iOS |
Stable |
Full native performance |
| Android |
Stable |
Full native performance |
| Web |
Stable |
PWA support |
| macOS |
Stable |
Native desktop |
| Windows |
Stable |
Native desktop |
| Linux |
Stable |
Native desktop |
Project Structure
my_app/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── features/
│ │ ├── home/
│ │ │ ├── home_screen.dart
│ │ │ ├── home_controller.dart
│ │ │ └── widgets/
│ │ └── settings/
│ ├── core/
│ │ ├── theme/
│ │ ├── utils/
│ │ └── constants/
│ └── shared/
│ ├── models/
│ ├── services/
│ └── widgets/
├── test/
├── pubspec.yaml
└── analysis_options.yaml
Basic Structure
Main Entry
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(
title: 'My App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
Stateless Widget
class GreetingCard extends StatelessWidget {
const GreetingCard({
super.key,
required this.name,
});
final String name;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Hello, $name!',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}
Stateful Widget
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Count: $_count',
style: Theme.of(context).textTheme.headlineLarge,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}
State Management
Riverpod (Recommended)
// Provider definition
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
// Usage in widget
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Text('Count: $count'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
// Async provider
final itemsProvider = FutureProvider<List<Item>>((ref) async {
final repository = ref.watch(repositoryProvider);
return repository.fetchItems();
});
BLoC Pattern
// Events
abstract class CounterEvent {}
class IncrementPressed extends CounterEvent {}
class DecrementPressed extends CounterEvent {}
// State
class CounterState {
final int count;
const CounterState(this.count);
}
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState(0)) {
on<IncrementPressed>((event, emit) {
emit(CounterState(state.count + 1));
});
on<DecrementPressed>((event, emit) {
emit(CounterState(state.count - 1));
});
}
}
// Usage
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Count: ${state.count}');
},
)
Navigation
GoRouter (Recommended)
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailsScreen(id: id);
},
),
],
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
),
],
);
// Usage
MaterialApp.router(
routerConfig: router,
)
// Navigate
context.go('/details/123');
context.push('/settings');
context.pop();
Common Widgets
Lists
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(item.imageUrl),
),
title: Text(item.title),
subtitle: Text(item.subtitle),
trailing: const Icon(Icons.chevron_right),
onTap: () => context.push('/details/${item.id}'),
);
},
)
Forms
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate()) {
// Process login
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || !value.contains('@')) {
return 'Enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: (value) {
if (value == null || value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _submit,
child: const Text('Login'),
),
],
),
);
}
}
Networking
Dio HTTP Client
class ApiService {
final Dio _dio;
ApiService() : _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
)) {
_dio.interceptors.add(LogInterceptor());
}
Future<List<Item>> getItems() async {
try {
final response = await _dio.get('/items');
return (response.data as List)
.map((json) => Item.fromJson(json))
.toList();
} on DioException catch (e) {
throw ApiException(e.message ?? 'Unknown error');
}
}
}
Local Storage
Hive (Recommended)
// Model
@HiveType(typeId: 0)
class Item extends HiveObject {
@HiveField(0)
late String id;
@HiveField(1)
late String name;
}
// Setup
await Hive.initFlutter();
Hive.registerAdapter(ItemAdapter());
await Hive.openBox<Item>('items');
// Usage
final box = Hive.box<Item>('items');
await box.put('key', item);
final item = box.get('key');
SharedPreferences
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', 'abc123');
final token = prefs.getString('token');
Platform-Specific Code
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
Widget build(BuildContext context) {
if (kIsWeb) {
return WebLayout();
} else if (Platform.isIOS) {
return CupertinoLayout();
} else if (Platform.isAndroid) {
return MaterialLayout();
} else if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
return DesktopLayout();
}
return DefaultLayout();
}
Testing
Widget Tests
testWidgets('Counter increments', (tester) async {
await tester.pumpWidget(const MaterialApp(home: Counter()));
expect(find.text('Count: 0'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('Count: 1'), findsOneWidget);
});
Unit Tests
test('Item fromJson parses correctly', () {
final json = {'id': '1', 'name': 'Test'};
final item = Item.fromJson(json);
expect(item.id, '1');
expect(item.name, 'Test');
});
Performance
Best Practices
// Use const constructors
const SizedBox(height: 16)
// Avoid rebuilds with const
const MyStaticWidget()
// Use ListView.builder for long lists
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(items[index]),
)
// Cache images
CachedNetworkImage(
imageUrl: url,
placeholder: (context, url) => const CircularProgressIndicator(),
)
Best Practices
DO:
- Use const constructors everywhere possible
- Split widgets into smaller components
- Use Riverpod or BLoC for state
- Follow Flutter naming conventions
- Write widget tests
DON'T:
- Put logic in build methods
- Create god widgets
- Use setState for complex state
- Ignore null safety
- Skip code generation setup