Flutter Advanced Widgets Guide
A comprehensive guide to advanced Flutter widgets, performance optimization techniques, and common patterns. This guide covers custom painting, animations, responsive design, and best practices for building complex UIs.
Table of Contents
- Custom Painting
- Animated Widgets
- Responsive Design
- Performance Optimization
- Common UI Patterns
- Advanced Techniques
- Debugging & Development
Custom Painting
Understanding CustomPaint
What it does: Allows you to draw custom graphics and shapes using the Canvas API.
When to use:
- Creating custom charts and graphs
- Drawing custom shapes and icons
- Building unique visual effects
- Performance-critical graphics
Basic CustomPainter
class CustomPainterWidget extends StatelessWidget {
Widget build(BuildContext context) {
return CustomPaint(
painter: MyCustomPainter(),
child: Container(width: 200, height: 200),
);
}
}
class MyCustomPainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
// Draw a circle
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
50.0,
paint,
);
}
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Advanced Custom Painting
Drawing Complex Shapes
class ComplexShapePainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
final path = Path();
path.moveTo(0, size.height * 0.5);
path.lineTo(size.width * 0.25, 0);
path.lineTo(size.width * 0.75, 0);
path.lineTo(size.width, size.height * 0.5);
path.lineTo(size.width * 0.75, size.height);
path.lineTo(size.width * 0.25, size.height);
path.close();
canvas.drawPath(path, paint);
}
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Animated Custom Painting
class AnimatedCustomPainter extends CustomPainter {
final double animationValue;
AnimatedCustomPainter(this.animationValue);
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 3.0
..style = PaintingStyle.stroke;
// Animate circle radius
final radius = 50.0 * animationValue;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
radius,
paint,
);
}
bool shouldRepaint(AnimatedCustomPainter oldDelegate) {
return oldDelegate.animationValue != animationValue;
}
}
// Usage with AnimationController
class AnimatedPainterWidget extends StatefulWidget {
_AnimatedPainterWidgetState createState() => _AnimatedPainterWidgetState();
}
class _AnimatedPainterWidgetState extends State<AnimatedPainterWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
_controller.repeat();
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return CustomPaint(
painter: AnimatedCustomPainter(_animation.value),
child: Container(width: 200, height: 200),
);
},
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
}
Animated Widgets
Understanding Animated Widgets
What they do: Provide smooth transitions between different widget states.
When to use:
- State changes that should be visually smooth
- User interactions that need feedback
- Loading states and transitions
- Creating engaging user experiences
Built-in Animated Widgets
AnimatedContainer
What it does: Automatically animates changes to its properties.
When to use:
- Smooth size, color, or position changes
- Layout transitions
- Interactive feedback
class AnimatedContainerExample extends StatefulWidget {
_AnimatedContainerExampleState createState() => _AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
bool _expanded = false;
Color _color = Colors.blue;
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
color: _color,
child: GestureDetector(
onTap: () {
setState(() {
_expanded = !_expanded;
_color = _expanded ? Colors.red : Colors.blue;
});
},
child: Center(
child: Text(
_expanded ? 'Expanded' : 'Tap to expand',
style: TextStyle(color: Colors.white),
),
),
),
);
}
}
AnimatedOpacity
What it does: Animates the opacity of a widget.
When to use:
- Fade in/out effects
- Loading states
- Smooth content transitions
class AnimatedOpacityExample extends StatefulWidget {
_AnimatedOpacityExampleState createState() => _AnimatedOpacityExampleState();
}
class _AnimatedOpacityExampleState extends State<AnimatedOpacityExample> {
bool _visible = true;
Widget build(BuildContext context) {
return Column(
children: [
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Container(
width: 200,
height: 100,
color: Colors.blue,
child: Center(child: Text('Fade me')),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_visible = !_visible;
});
},
child: Text(_visible ? 'Hide' : 'Show'),
),
],
);
}
}
AnimatedPositioned
What it does: Animates the position of a widget within a Stack.
When to use:
- Sliding animations
- Floating elements
- Position-based interactions
class AnimatedPositionedExample extends StatefulWidget {
_AnimatedPositionedExampleState createState() => _AnimatedPositionedExampleState();
}
class _AnimatedPositionedExampleState extends State<AnimatedPositionedExample> {
bool _moved = false;
Widget build(BuildContext context) {
return Container(
width: 300,
height: 200,
child: Stack(
children: [
AnimatedPositioned(
duration: Duration(milliseconds: 300),
left: _moved ? 200 : 0,
top: _moved ? 100 : 0,
child: Container(
width: 100,
height: 100,
color: Colors.red,
child: Center(child: Text('Move me')),
),
),
Positioned(
bottom: 0,
left: 0,
child: ElevatedButton(
onPressed: () {
setState(() {
_moved = !_moved;
});
},
child: Text(_moved ? 'Move Back' : 'Move'),
),
),
],
),
);
}
}
Custom Animated Widgets
AnimatedBuilder Pattern
class CustomAnimatedWidget extends StatefulWidget {
_CustomAnimatedWidgetState createState() => _CustomAnimatedWidgetState();
}
class _CustomAnimatedWidgetState extends State<CustomAnimatedWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<Color?> _colorAnimation;
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 1.2,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
));
_colorAnimation = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(_controller);
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
width: 100,
height: 100,
color: _colorAnimation.value,
child: GestureDetector(
onTap: () {
if (_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: Center(
child: Text(
'Tap me',
style: TextStyle(color: Colors.white),
),
),
),
),
);
},
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
}
Responsive Design
Understanding Responsive Design
What it does: Adapts your UI to different screen sizes and orientations.
When to use:
- Supporting multiple device sizes
- Tablet and desktop layouts
- Landscape/portrait orientations
- Accessibility considerations
MediaQuery Usage
Screen Size Detection
class ResponsiveWidget extends StatelessWidget {
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
final screenWidth = screenSize.width;
final screenHeight = screenSize.height;
// Responsive layout based on screen width
if (screenWidth < 600) {
// Mobile layout
return Column(
children: [
Container(
width: screenWidth * 0.9,
height: screenHeight * 0.3,
color: Colors.blue,
child: Center(child: Text('Mobile Layout')),
),
SizedBox(height: 20),
Container(
width: screenWidth * 0.9,
height: screenHeight * 0.3,
color: Colors.green,
child: Center(child: Text('Mobile Content')),
),
],
);
} else {
// Tablet/Desktop layout
return Row(
children: [
Container(
width: screenWidth * 0.3,
height: screenHeight * 0.8,
color: Colors.blue,
child: Center(child: Text('Sidebar')),
),
SizedBox(width: 20),
Expanded(
child: Container(
height: screenHeight * 0.8,
color: Colors.green,
child: Center(child: Text('Main Content')),
),
),
],
);
}
}
}
Orientation Detection
class OrientationResponsiveWidget extends StatelessWidget {
Widget build(BuildContext context) {
final orientation = MediaQuery.of(context).orientation;
return orientation == Orientation.portrait
? _buildPortraitLayout()
: _buildLandscapeLayout();
}
Widget _buildPortraitLayout() {
return Column(
children: [
Container(
height: 200,
color: Colors.blue,
child: Center(child: Text('Portrait Header')),
),
Expanded(
child: Container(
color: Colors.green,
child: Center(child: Text('Portrait Content')),
),
),
],
);
}
Widget _buildLandscapeLayout() {
return Row(
children: [
Container(
width: 200,
color: Colors.blue,
child: Center(child: Text('Landscape Sidebar')),
),
Expanded(
child: Container(
color: Colors.green,
child: Center(child: Text('Landscape Content')),
),
),
],
);
}
}
LayoutBuilder for Responsive Design
class LayoutBuilderWidget extends StatelessWidget {
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// Use constraints to make layout decisions
if (constraints.maxWidth < 600) {
return _buildMobileLayout(constraints);
} else if (constraints.maxWidth < 900) {
return _buildTabletLayout(constraints);
} else {
return _buildDesktopLayout(constraints);
}
},
);
}
Widget _buildMobileLayout(BoxConstraints constraints) {
return Column(
children: [
Container(
width: constraints.maxWidth,
height: constraints.maxHeight * 0.3,
color: Colors.red,
child: Center(child: Text('Mobile Header')),
),
Expanded(
child: Container(
width: constraints.maxWidth,
color: Colors.blue,
child: Center(child: Text('Mobile Content')),
),
),
],
);
}
Widget _buildTabletLayout(BoxConstraints constraints) {
return Row(
children: [
Container(
width: constraints.maxWidth * 0.3,
height: constraints.maxHeight,
color: Colors.orange,
child: Center(child: Text('Tablet Sidebar')),
),
Expanded(
child: Container(
color: Colors.purple,
child: Center(child: Text('Tablet Content')),
),
),
],
);
}
Widget _buildDesktopLayout(BoxConstraints constraints) {
return Row(
children: [
Container(
width: constraints.maxWidth * 0.2,
height: constraints.maxHeight,
color: Colors.grey,
child: Center(child: Text('Desktop Sidebar')),
),
Expanded(
child: Column(
children: [
Container(
height: constraints.maxHeight * 0.2,
color: Colors.yellow,
child: Center(child: Text('Desktop Header')),
),
Expanded(
child: Container(
color: Colors.cyan,
child: Center(child: Text('Desktop Content')),
),
),
],
),
),
],
);
}
}
Performance Optimization
Understanding Performance in Flutter
Key Concepts:
- Rebuilds: When widgets rebuild unnecessarily
- Memory: Managing widget lifecycle and memory usage
- Rendering: Optimizing the rendering pipeline
const Constructors
What they do: Create immutable widgets that don't rebuild.
When to use:
- Static widgets that don't change
- Widgets in lists that don't depend on state
- Performance-critical scenarios
// Good: Use const for static widgets
class OptimizedWidget extends StatelessWidget {
const OptimizedWidget({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Column(
children: [
const Text('Static text that never changes'),
const Icon(Icons.star, color: Colors.yellow),
const SizedBox(height: 16.0),
const Divider(),
// Dynamic content
Text('Dynamic: ${DateTime.now()}'),
],
);
}
}
// Avoid: Non-const widgets that could be const
class NonOptimizedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: [
Text('Static text'), // Should be const
Icon(Icons.star), // Should be const
SizedBox(height: 16.0), // Should be const
],
);
}
}
ListView Optimization
ListView.builder for Large Lists
// Good: Use ListView.builder for large lists
class OptimizedListView extends StatelessWidget {
final List<String> items;
const OptimizedListView({Key? key, required this.items}) : super(key: key);
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(items[index]), // Important for state preservation
title: Text(items[index]),
subtitle: Text('Item $index'),
);
},
);
}
}
// Avoid: ListView with all items in memory
class NonOptimizedListView extends StatelessWidget {
Widget build(BuildContext context) {
return ListView(
children: List.generate(10000, (index) {
return ListTile(
title: Text('Item $index'),
);
}),
);
}
}
AutomaticKeepAliveClientMixin
// Keep tab content alive when switching tabs
class TabViewWithKeepAlive extends StatefulWidget {
_TabViewWithKeepAliveState createState() => _TabViewWithKeepAliveState();
}
class _TabViewWithKeepAliveState extends State<TabViewWithKeepAlive>
with SingleTickerProviderStateMixin {
late TabController _tabController;
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
KeepAliveTab(child: Tab1Content()),
KeepAliveTab(child: Tab2Content()),
KeepAliveTab(child: Tab3Content()),
],
),
);
}
void dispose() {
_tabController.dispose();
super.dispose();
}
}
class KeepAliveTab extends StatefulWidget {
final Widget child;
const KeepAliveTab({Key? key, required this.child}) : super(key: key);
_KeepAliveTabState createState() => _KeepAliveTabState();
}
class _KeepAliveTabState extends State<KeepAliveTab>
with AutomaticKeepAliveClientMixin {
bool get wantKeepAlive => true;
Widget build(BuildContext context) {
super.build(context); // Required by AutomaticKeepAliveClientMixin
return widget.child;
}
}
Widget Extraction
Extract Reusable Widgets
// Good: Extract reusable widgets
class UserCard extends StatelessWidget {
final String name;
final String email;
final String? avatarUrl;
final VoidCallback? onTap;
const UserCard({
Key? key,
required this.name,
required this.email,
this.avatarUrl,
this.onTap,
}) : super(key: key);
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl!) : null,
child: avatarUrl == null ? Text(name[0]) : null,
),
title: Text(name),
subtitle: Text(email),
onTap: onTap,
),
);
}
}
// Usage
class UserList extends StatelessWidget {
final List<User> users;
const UserList({Key? key, required this.users}) : super(key: key);
Widget build(BuildContext context) {
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return UserCard(
name: user.name,
email: user.email,
avatarUrl: user.avatarUrl,
onTap: () => _onUserTap(user),
);
},
);
}
void _onUserTap(User user) {
// Handle user tap
}
}
Common UI Patterns
Loading States
Loading Widget Pattern
class LoadingWidget extends StatelessWidget {
final bool isLoading;
final Widget child;
final Widget? loadingWidget;
const LoadingWidget({
Key? key,
required this.isLoading,
required this.child,
this.loadingWidget,
}) : super(key: key);
Widget build(BuildContext context) {
return isLoading
? loadingWidget ?? const Center(
child: CircularProgressIndicator(),
)
: child;
}
}
// Usage
class DataScreen extends StatelessWidget {
final bool isLoading;
final List<String> data;
const DataScreen({
Key? key,
required this.isLoading,
required this.data,
}) : super(key: key);
Widget build(BuildContext context) {
return LoadingWidget(
isLoading: isLoading,
child: ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(data[index]));
},
),
);
}
}
Empty States
Empty State Widget
class EmptyStateWidget extends StatelessWidget {
final String message;
final IconData icon;
final VoidCallback? onRetry;
final String? retryText;
const EmptyStateWidget({
Key? key,
required this.message,
this.icon = Icons.inbox,
this.onRetry,
this.retryText,
}) : super(key: key);
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 64.0,
color: Colors.grey[400],
),
SizedBox(height: 16.0),
Text(
message,
style: TextStyle(
color: Colors.grey[600],
fontSize: 16.0,
),
textAlign: TextAlign.center,
),
if (onRetry != null) ...[
SizedBox(height: 16.0),
ElevatedButton(
onPressed: onRetry,
child: Text(retryText ?? 'Retry'),
),
],
],
),
);
}
}
// Usage
class DataList extends StatelessWidget {
final bool isLoading;
final List<String> data;
final VoidCallback onRetry;
const DataList({
Key? key,
required this.isLoading,
required this.data,
required this.onRetry,
}) : super(key: key);
Widget build(BuildContext context) {
if (isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (data.isEmpty) {
return EmptyStateWidget(
message: 'No data available',
icon: Icons.data_usage,
onRetry: onRetry,
retryText: 'Load Data',
);
}
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(data[index]));
},
);
}
}
Error States
Error Widget Pattern
class ErrorWidget extends StatelessWidget {
final String message;
final String? details;
final VoidCallback? onRetry;
final IconData icon;
const ErrorWidget({
Key? key,
required this.message,
this.details,
this.onRetry,
this.icon = Icons.error_outline,
}) : super(key: key);
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 64.0,
color: Colors.red[400],
),
SizedBox(height: 16.0),
Text(
message,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
color: Colors.red[700],
),
textAlign: TextAlign.center,
),
if (details != null) ...[
SizedBox(height: 8.0),
Text(
details!,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14.0,
),
textAlign: TextAlign.center,
),
],
if (onRetry != null) ...[
SizedBox(height: 16.0),
ElevatedButton(
onPressed: onRetry,
style: ElevatedButton.styleFrom(
primary: Colors.red[600],
),
child: Text('Retry'),
),
],
],
),
),
);
}
}
Conditional Rendering
Conditional Widget Patterns
class ConditionalRenderingExample extends StatelessWidget {
final bool isLoggedIn;
final bool hasData;
final String? errorMessage;
const ConditionalRenderingExample({
Key? key,
required this.isLoggedIn,
required this.hasData,
this.errorMessage,
}) : super(key: key);
Widget build(BuildContext context) {
return Column(
children: [
// Always shown
Text('Welcome to the app'),
// Conditional with if statement
if (isLoggedIn) ...[
SizedBox(height: 16),
Text('You are logged in'),
ElevatedButton(
onPressed: () => print('Logout'),
child: Text('Logout'),
),
],
// Conditional with ternary operator
SizedBox(height: 16),
hasData
? Text('Data loaded successfully')
: Text('No data available'),
// Conditional with null check
if (errorMessage != null) ...[
SizedBox(height: 16),
Container(
padding: EdgeInsets.all(8),
color: Colors.red[100],
child: Text(
errorMessage!,
style: TextStyle(color: Colors.red[800]),
),
),
],
],
);
}
}
Advanced Techniques
Theme Usage
Accessing Theme Data
class ThemedWidget extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final colorScheme = theme.colorScheme;
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: colorScheme.surface,
border: Border.all(color: colorScheme.outline),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Themed Title',
style: textTheme.headline6?.copyWith(
color: colorScheme.primary,
),
),
SizedBox(height: 8),
Text(
'Themed body text',
style: textTheme.bodyText2?.copyWith(
color: colorScheme.onSurface,
),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
primary: colorScheme.primary,
onPrimary: colorScheme.onPrimary,
),
child: Text('Themed Button'),
),
],
),
);
}
}
Custom Theme
class CustomThemeWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
primaryColor: Colors.purple,
colorScheme: ColorScheme.light(
primary: Colors.purple,
secondary: Colors.orange,
surface: Colors.white,
onSurface: Colors.black,
),
textTheme: Theme.of(context).textTheme.copyWith(
headline6: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.purple,
),
),
),
child: Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Custom themed text'),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {},
child: Text('Custom themed button'),
),
],
),
),
);
}
}
Key Usage
ValueKey for Lists
class KeyUsageExample extends StatelessWidget {
final List<User> users;
const KeyUsageExample({Key? key, required this.users}) : super(key: key);
Widget build(BuildContext context) {
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
// Important: Use unique keys for stateful widgets in lists
key: ValueKey(user.id),
title: Text(user.name),
subtitle: Text(user.email),
leading: CircleAvatar(
child: Text(user.name[0]),
),
);
},
);
}
}
GlobalKey for Forms
class FormWithGlobalKey extends StatefulWidget {
_FormWithGlobalKeyState createState() => _FormWithGlobalKeyState();
}
class _FormWithGlobalKeyState extends State<FormWithGlobalKey> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter an email';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a password';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Form is valid, proceed with submission
print('Email: ${_emailController.text}');
print('Password: ${_passwordController.text}');
}
},
child: Text('Submit'),
),
],
),
);
}
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
}
Debugging & Development
Debug Widgets
Debug Paint
// Enable debug paint to see widget boundaries
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true; // Shows widget boundaries
debugPaintBaselinesEnabled = true; // Shows text baselines
debugPaintPointersEnabled = true; // Shows pointer events
runApp(MyApp());
}
Debug Banner
// Remove debug banner in production
MaterialApp(
debugShowCheckedModeBanner: false, // Removes debug banner
title: 'My App',
home: MyHomePage(),
)
Error Handling
Error Boundary Widget
class ErrorBoundary extends StatelessWidget {
final Widget child;
final Widget Function(FlutterErrorDetails)? errorBuilder;
const ErrorBoundary({
Key? key,
required this.child,
this.errorBuilder,
}) : super(key: key);
Widget build(BuildContext context) {
return ErrorWidget.builder = (FlutterErrorDetails details) {
return errorBuilder?.call(details) ??
Container(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
color: Colors.red,
size: 48.0,
),
SizedBox(height: 16.0),
Text(
'Something went wrong',
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.0),
Text(
details.exception.toString(),
style: TextStyle(fontSize: 14.0),
textAlign: TextAlign.center,
),
],
),
);
};
}
}
// Usage
class AppWithErrorBoundary extends StatelessWidget {
Widget build(BuildContext context) {
return ErrorBoundary(
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
Performance Monitoring
Widget Rebuild Tracking
class RebuildTracker extends StatelessWidget {
final Widget child;
final String widgetName;
const RebuildTracker({
Key? key,
required this.child,
required this.widgetName,
}) : super(key: key);
Widget build(BuildContext context) {
print('$widgetName rebuilt at ${DateTime.now()}');
return child;
}
}
// Usage
class MyWidget extends StatelessWidget {
Widget build(BuildContext context) {
return RebuildTracker(
widgetName: 'MyWidget',
child: Container(
child: Text('Track rebuilds'),
),
);
}
}
References
- Flutter Performance (flutter.dev)
- Flutter Custom Painting (flutter.dev)
- Flutter Animations (flutter.dev)
- Flutter Performance Best Practices (flutter.dev)
- Flutter Widget Catalog (flutter.dev)