Dart Basics, Widgets, Layout, Navigation, State Management, Networking, Animations — complete Flutter cross-platform development reference.
// ── Variables ──
var name = 'Flutter'; // mutable, type inferred
final version = '3.22'; // runtime constant (can be computed)
const pi = 3.14159; // compile-time constant
String description = 'Cross-platform'; // explicit type
// ── Types ──
int count = 42;
double price = 19.99;
bool isActive = true;
String text = 'Hello \$name'; // string interpolation
String multiline = '''
This is a
multi-line string
''';
int? maybeNull = null; // nullable type
// ── Type Safety ──
// Sound null safety (Dart 2.12+)
String mustHave = 'value'; // non-nullable
String? canBeNull = null; // nullable
String definitely = canBeNull ?? 'default'; // null coalescing
int length = mustHave.length; // safe — non-nullable
// ── Collections ──
var list = <int>[1, 2, 3];
list.add(4);
list.insert(1, 99);
list.removeAt(0);
list.addAll([5, 6]);
list.where((x) => x > 2).toList(); // [3, 4, 5, 6]
list.map((x) => x * 2).toList(); // [2, 198, 6, 8, 10, 12]
list.reduce((a, b) => a + b); // sum
var set = <String>{'apple', 'banana', 'cherry'};
set.add('date');
set.contains('apple');
var map = <String, int>{'alice': 95, 'bob': 87};
map['charlie'] = 72;
map.update('alice', (v) => v + 5);
map.remove('bob');
// ── Control Flow ──
if (count > 10) {
} else if (count > 5) {
} else {}
// Ternary
String label = count > 0 ? 'positive' : 'non-positive';
// for loops
for (var i = 0; i < 10; i++) {}
for (var item in list) {}
for (final entry in map.entries) {}
// Switch with expressions (Dart 3)
var result = switch (grade) {
'A' || 'B' => 'Good',
'C' => 'Average',
_ => 'Fail',
};// ── Functions ──
int add(int a, int b) => a + b; // arrow function
int multiply(int a, int b) { // regular function
return a * b;
}
// Named parameters
void greet({required String name, int age = 0}) {
print('Hello $name, age $age');
}
greet(name: 'Alice', age: 30);
greet(name: 'Bob'); // age defaults to 0
// Positional optional parameters
int sum(int a, [int b = 0, int c = 0]) => a + b + c;
// First-class functions
void apply(int Function(int) fn, int value) {
print(fn(value));
}
apply((x) => x * 2, 5); // 10
// Closures
var counter = 0;
var increment = () {
counter++;
return counter;
};
// ── Classes ──
class User {
final String name;
int age;
String _email; // private (library-private)
// Constructor
User({required this.name, this.age = 0, String? email})
: _email = email ?? '';
// Named constructor
User.guest() : name = 'Guest', age = 0, _email = '';
// Factory constructor
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
age: json['age'] as int? ?? 0,
);
}
// Getter / Setter
String get email => _email;
set email(String value) {
if (value.contains('@')) _email = value;
}
// Methods
void greet() => print('Hello, $name');
}
// ── Extension methods ──
extension StringX on String {
String get capitalized =>
isEmpty ? '' : '${this[0].toUpperCase()}${substring(1)}';
bool get isEmail => RegExp(r'^[\w-.]+@[\w-.]+\.[a-z]{2,}$').hasMatch(this);
}
'hello'.capitalized; // "Hello"
'test@email.com'.isEmail; // true
// ── Enums (Dart 3) ──
enum StatusCode { ok, notFound, serverError }
enum Vehicle { car(tires: 4), bike(tires: 2); final int tires; }
print(Vehicle.car.tires); // 4| Type | Default | Nullable |
|---|---|---|
| int | 0 | int? |
| double | 0.0 | double? |
| bool | false | bool? |
| String | '' | String? |
| List<T> | <T>[] | List<T>? |
| Map<K,V> | {} | Map<K,V>? |
import 'package:flutter/material.dart';
// ── StatelessWidget ──
// Immutable — UI described by current configuration
class CounterDisplay extends StatelessWidget {
final int count;
final VoidCallback onIncrement;
const CounterDisplay({
super.key,
required this.count,
required this.onIncrement,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $count',
style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 16),
ElevatedButton(
onPressed: onIncrement,
child: const Text('Increment'),
),
],
),
),
);
}
}
// ── StatefulWidget ──
// Mutable — internal state can change over time
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0; // private mutable state
void _increment() {
setState(() { // trigger rebuild
_count++;
});
}
@override
Widget build(BuildContext context) {
return CounterDisplay(
count: _count,
onIncrement: _increment,
);
}
}// ── Common Widgets ──
// Text
Text('Hello World',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
)
// Container
Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.blue),
boxShadow: [BoxShadow(blurRadius: 8, color: Colors.black26)],
),
child: const Text('Styled Container'),
)
// Row & Column
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(Icons.star, color: Colors.amber),
Text('Favorite'),
Spacer(), // takes remaining space
Text('Details'),
],
)
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Title', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 8), // spacing
const Text('Subtitle'),
],
)
// Image
Image.network('https://example.com/image.png',
width: 200, height: 200, fit: BoxFit.cover),
Image.asset('assets/images/logo.png'),
// Icon
Icon(Icons.home, size: 32, color: Colors.blue),
// ElevatedButton / TextButton / OutlinedButton
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text('Click Me'),
)
// Card
Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(children: [
const Text('Card Title', style: TextStyle(fontSize: 18)),
const SizedBox(height: 8),
const Text('Card content goes here'),
]),
),
)
// ListView.builder (efficient for long lists)
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
leading: const Icon(Icons.article),
title: Text(item.title),
subtitle: Text(item.description),
trailing: const Icon(Icons.chevron_right),
onTap: () => navigateTo(item),
);
},
)| Feature | StatelessWidget | StatefulWidget |
|---|---|---|
| State | None (config only) | Mutable internal state |
| Rebuild | When parent rebuilds | When setState() called |
| Use for | Display data, static UI | Forms, counters, animations |
| Performance | Better (no State) | Overhead of State object |
// ── Row & Column ──
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // horizontal
crossAxisAlignment: CrossAxisAlignment.center, // vertical
children: const [
Text('Left'),
Text('Center'),
Text('Right'),
],
)
// MainAxisAlignment options:
// start, center, end, spaceBetween, spaceAround, spaceEvenly
// CrossAxisAlignment options:
// start, center, end, stretch, baseline
// ── Expanded & Flexible ──
Row(
children: [
Expanded(flex: 2, child: Container(color: Colors.red, height: 40)),
Expanded(flex: 1, child: Container(color: Colors.green, height: 40)),
Expanded(flex: 1, child: Container(color: Colors.blue, height: 40)),
// 50% : 25% : 25% width distribution
],
)
// Flexible — takes remaining space but can be smaller
Flexible(
fit: FlexFit.loose, // or FlexFit.tight (fills available space)
child: Container(color: Colors.orange, height: 40),
)
// ── Stack — layered widgets ──
SizedBox(
width: 200,
height: 200,
child: Stack(
children: [
Container(color: Colors.blue), // bottom
Positioned( // positioned
left: 10, top: 10,
child: Container(width: 100, height: 100, color: Colors.red),
),
const Center(child: Text('On top')), // top
],
),
)
// ── Wrap — auto-flow layout ──
Wrap(
spacing: 8,
runSpacing: 4,
children: [
Chip(label: const Text('Dart')),
Chip(label: const Text('Flutter')),
Chip(label: const Text('Firebase')),
Chip(label: const Text('Material')),
Chip(label: const Text('iOS')),
Chip(label: const Text('Android')),
],
)
// ── SizedBox & Spacer ──
const SizedBox(height: 16), // vertical spacing
const SizedBox(width: 8), // horizontal spacing
const Spacer(), // push to edges (like flex: 1)
// ── ListView ──
ListView( // simple list
padding: const EdgeInsets.all(8),
children: items.map((item) => ListTile(title: Text(item))).toList(),
)
ListView.separated( // list with dividers
itemCount: items.length,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
)// ── CustomScrollView & Slivers ──
CustomScrollView(
slivers: [
const SliverAppBar.large(
title: Text('Profile'),
floating: true,
pinned: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(url, fit: BoxFit.cover),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Item $index')),
childCount: 50,
),
),
],
)
// ── GridView ──
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 1.0,
),
itemCount: items.length,
itemBuilder: (context, index) => Card(child: Text(items[index])),
)
// ── IntrinsicHeight — size children to match tallest ──
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(width: 100, color: Colors.red),
Container(width: 100, color: Colors.green),
],
),
)
// ── LayoutBuilder — responsive layouts ──
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return _buildWideLayout(); // tablet/desktop
}
return _buildNarrowLayout(); // phone
},
)
// ── MediaQuery — screen info ──
final size = MediaQuery.of(context).size;
final padding = MediaQuery.of(context).padding;
final brightness = MediaQuery.of(context).platformBrightness;
bool isDark = brightness == Brightness.dark;
// ── FittedBox — scale child to fit ──
FittedBox(
fit: BoxFit.contain, // or .cover, .fill, .scaleDown
child: Text('Very long text that needs to fit',
style: const TextStyle(fontSize: 100)),
)| Need | Widget | Notes |
|---|---|---|
| Horizontal | Row | Main axis is horizontal |
| Vertical | Column | Main axis is vertical |
| Overlap | Stack | Z-index layering |
| Wrap flow | Wrap | Auto-wrap to next line |
| Proportional | Expanded | flex: N for ratio |
| Responsive | LayoutBuilder | Constraint-based layout |
| Scrollable | ListView | Infinite scroll content |
| Grid | GridView | 2D grid layout |
// ── Provider (simple, widely used) ──
// pubspec.yaml: provider: ^6.0
import 'package:provider/provider.dart';
// Model (ChangeNotifier)
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // trigger UI rebuild
}
}
// Provide at app level
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const MyApp(),
),
);
}
// Consume in widget
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
// Read value (listen to changes)
Text('${context.watch<CounterModel>().count}',
style: Theme.of(context).textTheme.headlineMedium),
// Read without listening
Text('${context.read<CounterModel>().count}'),
ElevatedButton(
onPressed: () => context.read<CounterModel>().increment(),
child: const Text('Increment'),
),
],
);
}
}
// Multi-provider
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthModel()),
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => ThemeModel()),
],
child: const MyApp(),
)// ── RiverPod (recommended, compile-time safe) ──
// pubspec.yaml: flutter_riverpod: ^2.4
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provider — read-only value
final helloProvider = Provider<String>((ref) => 'Hello');
// StateNotifierProvider — mutable state
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
final counterProvider = StateNotifierProvider<CounterNotifier, int>(
(ref) => CounterNotifier(),
);
// FutureProvider — async data
final userProvider = FutureProvider.autoDispose<User>((ref) async {
final repository = ref.watch(userRepositoryProvider);
return repository.fetchCurrentUser();
});
// StreamProvider — real-time data
final messagesProvider = StreamProvider.autoDispose<List<Message>>((ref) {
return ref.watch(chatServiceProvider).getMessages();
});
// Consume in widgets
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count',
style: Theme.of(context).textTheme.headlineMedium);
}
}
// Consumer in subtree (only rebuilds subtree)
Consumer(builder: (context, ref, child) {
final count = ref.watch(counterProvider);
return Text('$count');
})
// ref.listen — side effects
ref.listen(counterProvider, (prev, next) {
if (next > 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Count exceeded 10!')),
);
}
});
// ProviderScope — required at root
void main() {
runApp(const ProviderScope(child: MyApp()));
}| Solution | Type | Complexity | Best For |
|---|---|---|---|
| setState | Local | Low | Simple widget state |
| Provider | Inherited | Low | Small/medium apps |
| Riverpod | Compile-safe | Medium | Production apps |
| BLoC | Stream-based | High | Large teams, testability |
| GetX | Simple | Low | Prototyping, small apps |
import 'dart:convert';
import 'package:http/http.dart' as http;
// ── Basic HTTP Request ──
class ApiService {
static const String baseUrl = 'https://api.example.com';
// GET request
Future<List<User>> fetchUsers() async {
final response = await http.get(
Uri.parse('$baseUrl/users'),
headers: {'Authorization': 'Bearer $token'},
);
if (response.statusCode == 200) {
final List<dynamic> json = jsonDecode(response.body);
return json.map((e) => User.fromJson(e)).toList();
} else {
throw Exception('Failed to load users: ${response.statusCode}');
}
}
// POST request
Future<User> createUser(Map<String, dynamic> data) async {
final response = await http.post(
Uri.parse('$baseUrl/users'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
);
if (response.statusCode == 201) {
return User.fromJson(jsonDecode(response.body));
}
throw Exception('Failed to create user');
}
// PUT request
Future<User> updateUser(int id, Map<String, dynamic> data) async {
final response = await http.put(
Uri.parse('$baseUrl/users/$id'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
);
return User.fromJson(jsonDecode(response.body));
}
// DELETE request
Future<void> deleteUser(int id) async {
final response = await http.delete(
Uri.parse('$baseUrl/users/$id'),
);
if (response.statusCode != 204) {
throw Exception('Failed to delete user');
}
}
}
// ── Error Handling ──
Future<void> safeFetch() async {
try {
final users = await ApiService().fetchUsers();
print('Loaded ${users.length} users');
} on SocketException {
print('No internet connection');
} on HttpException {
print('Server error');
} catch (e) {
print('Unexpected error: $e');
}
}// ── Dio (advanced HTTP client) ──
// pubspec.yaml: dio: ^5.4
import 'package:dio/dio.dart';
class DioClient {
late final Dio _dio;
DioClient() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3),
headers: {'Content-Type': 'application/json'},
));
// Interceptors
_dio.interceptors.addAll([
AuthInterceptor(),
LoggingInterceptor(),
]);
}
Future<List<User>> getUsers() async {
final response = await _dio.get('/users');
return (response.data as List).map((e) => User.fromJson(e)).toList();
}
}
// ── Auth Interceptor ──
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final token = TokenStorage.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.response?.statusCode == 401) {
// Refresh token logic
TokenStorage.refresh();
}
handler.next(err);
}
}
// ── Retry ──
Future<Response> fetchWithRetry(String path) async {
return await _dio.get(path).catchError((error) async {
if (error is DioException && error.type == DioExceptionType.connectionTimeout) {
return await _dio.get(path); // retry once
}
rethrow;
});
}| Feature | http | Dio |
|---|---|---|
| Interceptors | No | Yes (request/response) |
| Timeout config | Manual | Built-in |
| FormData | Manual | Built-in |
| Cancel requests | No | CancelToken |
| Retry | Manual | RetryInterceptor |
| Logging | Manual | LogInterceptor |
| Cache | No | CacheInterceptor (dio_cache) |
import 'package:flutter/material.dart';
// ── Implicit Animations (simplest) ──
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
decoration: BoxDecoration(
color: _expanded ? Colors.blue : Colors.green,
borderRadius: BorderRadius.circular(_expanded ? 32 : 8),
),
child: const Center(child: Text('Tap me')),
)
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: const Text('Fade in/out'),
)
AnimatedPadding(
padding: EdgeInsets.all(_padded ? 32 : 8),
duration: const Duration(milliseconds: 200),
child: Container(color: Colors.blue),
)
AnimatedDefaultTextStyle(
style: TextStyle(fontSize: _large ? 32 : 16),
duration: const Duration(milliseconds: 200),
child: const Text('Animated text'),
)
// ── AnimatedSwitcher ──
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) => ScaleTransition(
scale: animation, child: child,
),
child: Text('$count', key: ValueKey<int>(count)),
)
// ── Explicit Animations ──
class AnimatedLogo extends StatefulWidget {
@override
State<AnimatedLogo> createState() => _AnimatedLogoState();
}
class _AnimatedLogoState extends State<AnimatedLogo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true); // loop
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);
}
@override
void dispose() {
_controller.dispose(); // always dispose!
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: ScaleTransition(
scale: _animation,
child: const FlutterLogo(size: 100),
),
);
}
}// ── Hero Animations (shared element transitions) ──
// Screen 1
Hero(
tag: 'profile-image',
child: CircleAvatar(radius: 50, child: Image.network(url)),
)
// Screen 2 — same tag
Hero(
tag: 'profile-image',
child: CircleAvatar(radius: 100, child: Image.network(url)),
)
// Flutter automatically animates between screens
// ── Tween Animations ──
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(seconds: 1),
curve: Curves.elasticOut,
builder: (context, value, child) {
return Opacity(opacity: value, child: child);
},
child: const Text('Fade in with bounce'),
)
// Custom tween
TweenAnimationBuilder<Size>(
tween: SizeTween(begin: const Size(100, 100), end: const Size(200, 200)),
duration: const Duration(milliseconds: 500),
builder: (context, size, child) {
return Container(width: size.width, height: size.height, color: Colors.blue);
},
)
// ── AnimationController patterns ──
// Loop: ..repeat()
// Once: ..forward() then ..reverse()
// Custom: _controller.animateTo(target)
// ── Curves ──
Curves.linear // straight line
Curves.easeIn // slow start
Curves.easeOut // slow end
Curves.easeInOut // slow start and end
Curves.elasticIn // overshoot start
Curves.elasticOut // overshoot end
Curves.bounceIn // bounce at start
Curves.decelerate // fast start, slow end
// ── Staggered Animation ──
class StaggeredAnimation extends StatefulWidget {
@override
State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
)..forward();
}
@override
Widget build(BuildContext context) {
return Column(
children: List.generate(5, (index) {
final start = index * 0.2;
final end = start + 0.4;
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _controller,
curve: Interval(start, end, curve: Curves.easeOut),
)),
child: ListTile(title: Text('Item $index')),
);
}),
);
}
}| Type | Control | Complexity |
|---|---|---|
| Implicit | Automatic on data change | Low — AnimatedContainer etc. |
| Explicit | Manual AnimationController | Medium — full control |
| Hero | Automatic shared element | Low — same tag on both screens |
| TweenBuilder | Declarative with Tween | Low — no controller needed |
| Rive / Lottie | Pre-built animations | Low — use JSON/asset files |
StatelessWidget is immutable — its configuration is passed in, and calling build() produces the same UI for the same props. StatefulWidget has internal mutable state via setState() that can change over time, triggering rebuilds. Use stateless whenever possible for better performance and testability.
Flutter uses a three-tree model: Widget (immutable configuration, blueprints), Element (instantiation, lifecycle), and RenderObject (layout + painting). When you call setState(), Flutter marks the element dirty, calls build() to create new widgets, then diffs them (reconciliation) and only repaints changed RenderObjects. This is the Virtual DOM concept — efficient minimal repaints.
Provider uses InheritedWidget under the hood and requires BuildContext to access providers. Riverpod is compile-time safe (no BuildContext needed), supports auto-dispose, can be used outside widgets, and catches more errors at compile time. Riverpod is the recommended evolution of Provider by the same author.
Keys help Flutter identify which widgets have changed when children are reordered. Use ValueKey when widgets have unique IDs (user IDs, item IDs). Use ObjectKey when the entire object is the identity. Without keys, Flutter matches children by position — reordering would reuse the wrong state.
Use Platform.isIOS / Platform.isAndroid for simple checks. For larger platform differences, create platform channels (MethodChannel) to communicate with native code (Kotlin/Swift). For file paths, use path_provider. For styles, use Theme.of(context).platform to detect the target platform.
const creates a compile-time constant widget. When Flutter rebuilds, it can skip rebuilding const widgets entirely — they are cached. Always use const for widgets that don't change: const Text('Hello'), const SizedBox(height: 16), const EdgeInsets.all(8). This significantly improves performance, especially in large lists.