Custom Widgets in Flutter: When and How to Build Them
Custom Widgets in Flutter: When and How to Build Them
Flutter is all about widgets. Everything you see on the screen — buttons, text, images, layouts — is a widget.
Sometimes the built-in widgets are not enough for your design or logic needs. That’s where custom widgets come in.
Let’s explore:
π When to create a custom widget
π ️ How to build one
π‘ Tips for writing clean, reusable components
What Is a Custom Widget?
A custom widget is a widget you build yourself by:
Combining existing Flutter widgets (composition)
Adding your own design or logic
Reusing across your app
You can think of it like creating your own LEGO piece by putting together smaller pieces.
When Should You Create a Custom Widget?
1. Repeating the Same UI Code
If you're copying and pasting the same widget code multiple times, it's time for a custom widget.
Example:
You show a styled card in multiple places. Wrap it in a custom widget like UserCard().
2. Making Code More Readable
Long widget trees get messy fast. Breaking them into smaller custom widgets improves readability.
// Instead of:
Column(
children: [
Text("Title"),
SizedBox(height: 10),
Row(
children: [
Icon(Icons.star),
Text("Rating"),
],
),
],
);
// Do this:
Column(
children: [
TitleText(),
RatingRow(),
],
);
3. Adding Custom Behavior or Styling
Want a button that animates or a card with conditional logic? Build a custom widget!
Example:
A button that changes color when clicked or expands when hovered.
4. Sharing Widgets Across Screens
If a component is used on multiple screens, wrap it in its own widget so it’s reusable and testable.
How to Build a Custom Widget in Flutter
There are 2 main types of custom widgets:
StatelessWidget
StatefulWidget
1. Custom Stateless Widget (No Internal State)
Use this when your widget doesn’t change over time.
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
const CustomButton({required this.label, required this.onPressed});
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
onPressed: onPressed,
child: Text(label),
);
}
}
Usage:
CustomButton(
label: 'Click Me',
onPressed: () {
print('Button pressed');
},
)
2. Custom Stateful Widget (With Internal State)
Use this when the widget has internal state or needs to respond to user actions.
class CounterBox extends StatefulWidget {
@override
_CounterBoxState createState() => _CounterBoxState();
}
class _CounterBoxState extends State<CounterBox> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: increment,
child: Text('Increment'),
),
],
);
}
}
Passing Data to Custom Widgets
You can pass values just like function parameters.
class GreetingCard extends StatelessWidget {
final String name;
GreetingCard({required this.name});
@override
Widget build(BuildContext context) {
return Text('Hello, $name!');
}
}
Best Practices for Custom Widgets
Practice Why It Helps
Use descriptive names UserTile, ProductCard, RatingStars
Keep widgets small Easier to test, read, and reuse
Prefer composition over inheritance Combine widgets instead of extending
Use const where possible Improves performance
Separate UI and logic Keeps components clean
Testing Custom Widgets
You can write widget tests for your custom widgets.
Example:
testWidgets('CustomButton displays correct label', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: CustomButton(
label: 'Test',
onPressed: () {},
),
),
);
expect(find.text('Test'), findsOneWidget);
});
Advanced Custom Widgets (Optional)
You can go beyond basic widgets by:
Using AnimatedContainer, GestureDetector, etc.
Creating reusable themes/styles
Building interactive components with custom gestures
Example: A star rating widget with touch input.
Organizing Custom Widgets
Recommended folder structure:
lib/
widgets/
custom_button.dart
product_card.dart
screens/
home_screen.dart
This keeps your project clean and scalable.
Example: Creating a Custom Info Card
class InfoCard extends StatelessWidget {
final IconData icon;
final String title;
final String subtitle;
const InfoCard({
required this.icon,
required this.title,
required this.subtitle,
});
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: Icon(icon),
title: Text(title),
subtitle: Text(subtitle),
),
);
}
}
Usage:
InfoCard(
icon: Icons.email,
title: 'Email',
subtitle: 'user@example.com',
)
Benefits of Using Custom Widgets
Code Reuse: Write once, use anywhere
Cleaner UI Code: Easier to read and update
Separation of Concerns: Logic and layout stay separate
Faster Debugging: Track issues more easily
Scalable Architecture: Great for growing apps
Final Thoughts
Custom widgets are the backbone of clean Flutter apps.
π Use them whenever:
You repeat code
Need reusable UI
Want to simplify your screens
They make your app modular, maintainable, and scalable.
Comments
Post a Comment