Question #26MediumFlutter BasicsImportant

Usage of extension in flutter

#flutter

Answer

Overview

Extension methods in Dart allow you to add new functionality to existing classes without modifying their source code or creating subclasses. They're particularly useful for adding helper methods to built-in types or third-party classes.


Syntax

dart
extension ExtensionName on TargetType {
  // Add methods, getters, setters, operators
}

Basic Examples

String Extensions

dart
extension StringExtensions on String {
  // Capitalize first letter
  String get capitalize {
    if (isEmpty) return this;
    return '${this[0].toUpperCase()}${substring(1)}';
  }
  
  // Check if email
  bool get isEmail {
    return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);
  }
  
  // Reverse string
  String get reverse {
    return split('').reversed.join('');
  }
  
  // Remove whitespace
  String get removeWhitespace {
    return replaceAll(RegExp(r'\s+'), '');
  }
}

// Usage
void main() {
  print('hello'.capitalize);        // Hello
  print('test@email.com'.isEmail);  // true
  print('flutter'.reverse);         // rettulf
  print('hello world'.removeWhitespace); // helloworld
}

Number Extensions

dart
extension IntExtensions on int {
  // Check if even
  bool get isEven => this % 2 == 0;
  
  // Check if odd
  bool get isOdd => this % 2 != 0;
  
  // Convert to currency format
  String get toCurrency => '\$${toStringAsFixed(2)}';
  
  // Create list of range
  List<int> to(int end) {
    return List.generate(end - this + 1, (i) => this + i);
  }
}

extension DoubleExtensions on double {
  // Convert to percentage
  String get toPercentage => '${(this * 100).toStringAsFixed(1)}%';
}

// Usage
void main() {
  print(5.isEven);        // false
  print(4.isOdd);         // false
  print(1500.toCurrency); // $1500.00
  print(1.to(5));         // [1, 2, 3, 4, 5]
  print(0.75.toPercentage); // 75.0%
}

DateTime Extensions

dart
extension DateTimeExtensions on DateTime {
  // Check if today
  bool get isToday {
    final now = DateTime.now();
    return year == now.year && month == now.month && day == now.day;
  }
  
  // Check if yesterday
  bool get isYesterday {
    final yesterday = DateTime.now().subtract(Duration(days: 1));
    return year == yesterday.year && 
           month == yesterday.month && 
           day == yesterday.day;
  }
  
  // Format as string
  String get formatted {
    return '${day.toString().padLeft(2, '0')}/'
           '${month.toString().padLeft(2, '0')}/$year';
  }
  
  // Add days
  DateTime addDays(int days) {
    return add(Duration(days: days));
  }
}

// Usage
void main() {
  final now = DateTime.now();
  print(now.isToday);           // true
  print(now.formatted);         // 10/03/2026
  print(now.addDays(5).formatted); // 15/03/2026
}

Flutter-Specific Extensions

BuildContext Extensions

dart
extension BuildContextExtensions on BuildContext {
  // Easy access to theme
  ThemeData get theme => Theme.of(this);
  
  // Easy access to text theme
  TextTheme get textTheme => Theme.of(this).textTheme;
  
  // Easy access to color scheme
  ColorScheme get colorScheme => Theme.of(this).colorScheme;
  
  // Screen size
  Size get screenSize => MediaQuery.of(this).size;
  
  // Screen width
  double get screenWidth => MediaQuery.of(this).size.width;
  
  // Screen height
  double get screenHeight => MediaQuery.of(this).size.height;
  
  // Is mobile
  bool get isMobile => screenWidth < 600;
  
  // Is tablet
  bool get isTablet => screenWidth >= 600 && screenWidth < 1200;
  
  // Is desktop
  bool get isDesktop => screenWidth >= 1200;
  
  // Show snackbar
  void showSnackBar(String message) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }
  
  // Navigate
  Future<T?> push<T>(Widget page) {
    return Navigator.push(
      this,
      MaterialPageRoute(builder: (_) => page),
    );
  }
  
  // Pop
  void pop<T>([T? result]) {
    Navigator.pop(this, result);
  }
}

// Usage
class MyWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Container(
      width: context.screenWidth * 0.8,
      child: Text(
        'Hello',
        style: context.textTheme.headlineMedium,
      ),
    );
  }
}

// Show snackbar
context.showSnackBar('Success!');

// Navigate
context.push(DetailsScreen());

// Check device type
if (context.isMobile) {
  // Mobile layout
} else if (context.isTablet) {
  // Tablet layout
}

Widget Extensions

dart
extension WidgetExtensions on Widget {
  // Add padding
  Widget padding(EdgeInsets insets) {
    return Padding(padding: insets, child: this);
  }
  
  // Add margin (using Container)
  Widget margin(EdgeInsets insets) {
    return Container(margin: insets, child: this);
  }
  
  // Center widget
  Widget get center {
    return Center(child: this);
  }
  
  // Expanded widget
  Widget get expanded {
    return Expanded(child: this);
  }
  
  // Flexible widget
  Widget flexible({int flex = 1}) {
    return Flexible(flex: flex, child: this);
  }
  
  // Tap gesture
  Widget onTap(VoidCallback onTap) {
    return GestureDetector(onTap: onTap, child: this);
  }
  
  // Visibility
  Widget visible(bool isVisible) {
    return Visibility(visible: isVisible, child: this);
  }
}

// Usage
Text('Hello')
  .padding(EdgeInsets.all(16))
  .onTap(() => print('Tapped'))
  .center;

Container(child: Text('Expand'))
  .expanded;

Icon(Icons.star)
  .visible(showStar);

List Extensions

dart
extension ListExtensions<T> on List<T> {
  // Get first or null
  T? get firstOrNull {
    return isEmpty ? null : first;
  }
  
  // Get last or null
  T? get lastOrNull {
    return isEmpty ? null : last;
  }
  
  // Separate with divider
  List<Widget> separatedBy(Widget separator) {
    if (isEmpty) return [];
    return [
      for (int i = 0; i < length; i++) ...[
        this[i] as Widget,
        if (i < length - 1) separator,
      ],
    ];
  }
}

// Usage
final numbers = [1, 2, 3];
print(numbers.firstOrNull); // 1

final emptyList = <int>[];
print(emptyList.firstOrNull); // null

// Separate widgets
Column(
  children: [
    Text('Item 1'),
    Text('Item 2'),
    Text('Item 3'),
  ].separatedBy(Divider()),
);

Complete Example

dart
import 'package:flutter/material.dart';

// String extensions
extension StringX on String {
  String get capitalize => isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
  bool get isEmail => RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);
}

// Context extensions
extension ContextX on BuildContext {
  double get width => MediaQuery.of(this).size.width;
  double get height => MediaQuery.of(this).size.height;
  
  void showSnack(String msg) {
    ScaffoldMessenger.of(this).showSnackBar(SnackBar(content: Text(msg)));
  }
}

// Widget extensions
extension WidgetX on Widget {
  Widget p(double value) => Padding(padding: EdgeInsets.all(value), child: this);
  Widget get center => Center(child: this);
}

class ExampleScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final email = 'test@example.com';
    
    return Scaffold(
      appBar: AppBar(title: Text('extension demo'.capitalize)),
      body: Column(
        children: [
          Text('Valid email: ${email.isEmail}').p(16),
          ElevatedButton(
            onPressed: () => context.showSnack('Extension works!'),
            child: Text('Show Snackbar'),
          ).center,
          Text('Width: ${context.width}').p(8),
        ],
      ),
    );
  }
}

Generic Extensions

dart
extension IterableExtensions<T> on Iterable<T> {
  // Map with index
  Iterable<R> mapIndexed<R>(R Function(int index, T item) f) {
    int index = 0;
    return map((item) => f(index++, item));
  }
  
  // Group by
  Map<K, List<T>> groupBy<K>(K Function(T) keyFn) {
    final map = <K, List<T>>{};
    for (var item in this) {
      final key = keyFn(item);
      (map[key] ??= []).add(item);
    }
    return map;
  }
}

// Usage
final items = ['apple', 'banana', 'apricot', 'berry'];
final grouped = items.groupBy((item) => item[0]);
// {a: [apple, apricot], b: [banana, berry]}

final indexed = items.mapIndexed((index, item) => '$index: $item');
// [0: apple, 1: banana, 2: apricot, 3: berry]

Best Practices

Tip: Use extensions to add convenience methods, not complex logic

✅ Do

dart
// Good - Simple, reusable helpers
extension StringHelpers on String {
  String get capitalize => /* simple logic */;
  bool get isEmail => /* validation */;
}

❌ Don't

dart
// Bad - Complex business logic in extension
extension UserExtensions on User {
  Future<void> saveToDatabase() async {
    // Complex logic - should be in a service
  }
}

Name Extensions Clearly

dart
// ✅ Good
extension StringFormatting on String {}
extension ContextHelpers on BuildContext {}

// ❌ Bad
extension Utils on String {} // Too generic
extension X on BuildContext {} // Not descriptive

Resources