Ahmet Balaman LogoAhmet Balaman

Flutter: Creating Our Own Widgets

personAhmet Balaman
calendar_today
FlutterWidgetStatelessWidgetStatefulWidgetConstructor

Creating Our Own Widgets in Flutter

By creating your own widgets in Flutter, you can make your code more modular, readable and reusable. You can create customizable widgets by taking parameters using constructors.

Why Should We Create Custom Widgets?

  1. Prevent Code Repetition: To use the same structure in different places
  2. Readability: Express complex structures with simple names
  3. Easy Maintenance: Make changes from a single place
  4. Testability: Test widgets separately

Live Demo: Creating Custom Widgets

Learn how to create your own widgets interactively:

Custom Widget with StatelessWidget

StatelessWidget is used for widgets without state (whose state doesn't change).

Basic Structure

class MyCustomWidget extends StatelessWidget {
  const MyCustomWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(16),
      child: Text('My Custom Widget'),
    );
  }
}

Usage

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyCustomWidget(),
        ),
      ),
    );
  }
}

Parameters with Constructor

You can take parameters in the constructor to make your widget customizable.

Simple Parameters

class CustomButton extends StatelessWidget {
  final String text;
  final Color color;
  final VoidCallback onPressed;

  const CustomButton({
    Key? key,
    required this.text,
    required this.color,
    required this.onPressed,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
      ),
      child: Text(text),
    );
  }
}

Usage

CustomButton(
  text: 'Save',
  color: Colors.blue,
  onPressed: () {
    print('Saved');
  },
)

Required and Optional Parameters

Required Parameters

class UserCard extends StatelessWidget {
  final String name;
  final String email;

  const UserCard({
    Key? key,
    required this.name,    // Required
    required this.email,   // Required
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(name, style: TextStyle(fontWeight: FontWeight.bold)),
            SizedBox(height: 4),
            Text(email, style: TextStyle(color: Colors.grey)),
          ],
        ),
      ),
    );
  }
}

Optional Parameters

class CustomCard extends StatelessWidget {
  final String title;
  final String? subtitle;  // Optional (nullable)
  final Color color;

  const CustomCard({
    Key? key,
    required this.title,
    this.subtitle,           // Optional
    this.color = Colors.blue, // Default value
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      color: color,
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: TextStyle(fontSize: 18)),
            if (subtitle != null) // Show if exists
              Text(subtitle!, style: TextStyle(fontSize: 14)),
          ],
        ),
      ),
    );
  }
}

Usage:

CustomCard(title: 'Title')  // No subtitle

CustomCard(
  title: 'Title',
  subtitle: 'Subtitle',
  color: Colors.green,
)

Widget Composition

You can break complex widgets into smaller parts.

Example: Blog Post Card

class BlogPostCard extends StatelessWidget {
  final String title;
  final String author;
  final String date;
  final String excerpt;

  const BlogPostCard({
    Key? key,
    required this.title,
    required this.author,
    required this.date,
    required this.excerpt,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.all(8),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildHeader(),
            SizedBox(height: 12),
            _buildTitle(),
            SizedBox(height: 8),
            _buildExcerpt(),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader() {
    return Row(
      children: [
        CircleAvatar(
          child: Text(author[0]),
        ),
        SizedBox(width: 8),
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(author, style: TextStyle(fontWeight: FontWeight.bold)),
            Text(date, style: TextStyle(fontSize: 12, color: Colors.grey)),
          ],
        ),
      ],
    );
  }

  Widget _buildTitle() {
    return Text(
      title,
      style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
    );
  }

  Widget _buildExcerpt() {
    return Text(
      excerpt,
      style: TextStyle(color: Colors.grey[700]),
      maxLines: 3,
      overflow: TextOverflow.ellipsis,
    );
  }
}

Best Practices

  1. Use const Constructor:
const MyWidget({Key? key}) : super(key: key);
  1. Give Meaningful Names:
// ✅ Good
class UserProfileCard extends StatelessWidget { ... }

// ❌ Bad
class Widget1 extends StatelessWidget { ... }
  1. Single Responsibility Principle:
    Each widget should do one thing.

  2. Write Documentation:

/// Card widget that displays user information.
/// 
/// [name] and [email] parameters are required.
/// [avatarUrl] parameter is optional.
class UserCard extends StatelessWidget { ... }

Summary

Creating custom widgets:

  • Extends StatelessWidget or StatefulWidget
  • Takes parameters in constructor
  • Required parameters are specified with required
  • Default values can be assigned
  • Flexible widgets are made with child parameter
  • Complex structures are simplified with composition

Benefits:

  • Prevents code repetition
  • Increases readability
  • Makes maintenance easier
  • Increases testability
  • Provides reusability

Creating widgets in Flutter is the foundation of writing clean and maintainable code. Consider turning every repeating structure into a widget!