Skip to main content

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

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