Back to Posts

Flutter Widget Patterns

11 min read

This guide covers common widget patterns and best practices for building reusable UI components in Flutter.

1. Composition Patterns

Custom Container

class CustomContainer extends StatelessWidget {
  final Widget child;
  final EdgeInsets padding;
  final Color backgroundColor;
  final double borderRadius;
  final BoxShadow? shadow;

  const CustomContainer({
    Key? key,
    required this.child,
    this.padding = const EdgeInsets.all(16),
    this.backgroundColor = Colors.white,
    this.borderRadius = 8,
    this.shadow,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: padding,
      decoration: BoxDecoration(
        color: backgroundColor,
        borderRadius: BorderRadius.circular(borderRadius),
        boxShadow: shadow != null ? [shadow!] : null,
      ),
      child: child,
    );
  }
}

// Usage
CustomContainer(
  child: Text('Hello World'),
  backgroundColor: Colors.blue,
  borderRadius: 12,
  shadow: BoxShadow(
    color: Colors.black.withOpacity(0.1),
    blurRadius: 8,
    offset: const Offset(0, 2),
  ),
)

Custom Card

class CustomCard extends StatelessWidget {
  final Widget child;
  final EdgeInsets padding;
  final Color backgroundColor;
  final double borderRadius;
  final VoidCallback? onTap;

  const CustomCard({
    Key? key,
    required this.child,
    this.padding = const EdgeInsets.all(16),
    this.backgroundColor = Colors.white,
    this.borderRadius = 8,
    this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Material(
      color: backgroundColor,
      borderRadius: BorderRadius.circular(borderRadius),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(borderRadius),
        child: Padding(
          padding: padding,
          child: child,
        ),
      ),
    );
  }
}

// Usage
CustomCard(
  child: Column(
    children: [
      Text('Title'),
      Text('Description'),
    ],
  ),
  onTap: () {
    // Handle tap
  },
)

2. Layout Patterns

Responsive Layout

class ResponsiveLayout extends StatelessWidget {
  final Widget mobile;
  final Widget? tablet;
  final Widget? desktop;

  const ResponsiveLayout({
    Key? key,
    required this.mobile,
    this.tablet,
    this.desktop,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth >= 1200) {
          return desktop ?? tablet ?? mobile;
        } else if (constraints.maxWidth >= 600) {
          return tablet ?? mobile;
        } else {
          return mobile;
        }
      },
    );
  }
}

// Usage
ResponsiveLayout(
  mobile: MobileLayout(),
  tablet: TabletLayout(),
  desktop: DesktopLayout(),
)

Custom Grid

class CustomGrid extends StatelessWidget {
  final List<Widget> children;
  final int crossAxisCount;
  final double spacing;
  final double runSpacing;

  const CustomGrid({
    Key? key,
    required this.children,
    this.crossAxisCount = 2,
    this.spacing = 8,
    this.runSpacing = 8,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: spacing,
      runSpacing: runSpacing,
      children: children,
    );
  }
}

// Usage
CustomGrid(
  children: [
    CustomCard(child: Text('Item 1')),
    CustomCard(child: Text('Item 2')),
    CustomCard(child: Text('Item 3')),
  ],
  crossAxisCount: 3,
)

3. State Management Patterns

InheritedWidget

class ThemeProvider extends InheritedWidget {
  final ThemeData theme;
  final VoidCallback toggleTheme;

  const ThemeProvider({
    Key? key,
    required this.theme,
    required this.toggleTheme,
    required Widget child,
  }) : super(key: key, child: child);

  static ThemeProvider of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ThemeProvider>()!;
  }

  @override
  bool updateShouldNotify(ThemeProvider oldWidget) {
    return theme != oldWidget.theme;
  }
}

// Usage
ThemeProvider(
  theme: ThemeData.light(),
  toggleTheme: () {
    // Toggle theme
  },
  child: MyApp(),
)

ValueNotifier

class CounterProvider extends ValueNotifier<int> {
  CounterProvider() : super(0);

  void increment() {
    value++;
  }

  void decrement() {
    value--;
  }
}

// Usage
final counterProvider = CounterProvider();

ValueListenableBuilder<int>(
  valueListenable: counterProvider,
  builder: (context, value, child) {
    return Text('Count: $value');
  },
)

4. Animation Patterns

Animated Container

class AnimatedCustomContainer extends StatefulWidget {
  final Widget child;
  final Duration duration;
  final Curve curve;

  const AnimatedCustomContainer({
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 300),
    this.curve = Curves.easeInOut,
  }) : super(key: key);

  @override
  _AnimatedCustomContainerState createState() => _AnimatedCustomContainerState();
}

class _AnimatedCustomContainerState extends State<AnimatedCustomContainer> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: widget.duration,
      curve: widget.curve,
      height: _isExpanded ? 200 : 100,
      child: widget.child,
    );
  }
}

// Usage
AnimatedCustomContainer(
  child: Text('Animated Content'),
  duration: Duration(milliseconds: 500),
  curve: Curves.easeOut,
)

Hero Animation

class HeroImage extends StatelessWidget {
  final String tag;
  final String imageUrl;
  final double width;
  final double height;

  const HeroImage({
    Key? key,
    required this.tag,
    required this.imageUrl,
    this.width = 100,
    this.height = 100,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Hero(
      tag: tag,
      child: Image.network(
        imageUrl,
        width: width,
        height: height,
        fit: BoxFit.cover,
      ),
    );
  }
}

// Usage
HeroImage(
  tag: 'image_1',
  imageUrl: 'https://example.com/image.jpg',
  width: 200,
  height: 200,
)

5. Form Patterns

Custom Form Field

class CustomFormField extends StatelessWidget {
  final String label;
  final String? Function(String?)? validator;
  final TextEditingController controller;
  final bool obscureText;
  final TextInputType? keyboardType;
  final Widget? prefixIcon;
  final Widget? suffixIcon;

  const CustomFormField({
    Key? key,
    required this.label,
    required this.validator,
    required this.controller,
    this.obscureText = false,
    this.keyboardType,
    this.prefixIcon,
    this.suffixIcon,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: controller,
      obscureText: obscureText,
      keyboardType: keyboardType,
      validator: validator,
      decoration: InputDecoration(
        labelText: label,
        prefixIcon: prefixIcon,
        suffixIcon: suffixIcon,
        border: const OutlineInputBorder(),
        errorStyle: const TextStyle(color: Colors.red),
      ),
    );
  }
}

// Usage
CustomFormField(
  label: 'Email',
  controller: emailController,
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Email is required';
    }
    return null;
  },
  keyboardType: TextInputType.emailAddress,
  prefixIcon: Icon(Icons.email),
)

Form Validation

class LoginForm extends StatefulWidget {
  @override
  _LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  String? _validateEmail(String? value) {
    if (value == null || value.isEmpty) {
      return 'Email is required';
    }
    if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
      return 'Please enter a valid email';
    }
    return null;
  }

  String? _validatePassword(String? value) {
    if (value == null || value.isEmpty) {
      return 'Password is required';
    }
    if (value.length < 8) {
      return 'Password must be at least 8 characters';
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          CustomFormField(
            label: 'Email',
            controller: _emailController,
            validator: _validateEmail,
            keyboardType: TextInputType.emailAddress,
            prefixIcon: Icon(Icons.email),
          ),
          CustomFormField(
            label: 'Password',
            controller: _passwordController,
            validator: _validatePassword,
            obscureText: true,
            prefixIcon: Icon(Icons.lock),
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                // Process form
              }
            },
            child: const Text('Login'),
          ),
        ],
      ),
    );
  }
}

Best Practices

  1. Composition

    • Use composition over inheritance
    • Break down complex widgets
    • Keep widgets focused
    • Reuse common patterns
  2. Layout

    • Use responsive layouts
    • Follow Material Design
    • Consider different screen sizes
    • Optimize for performance
  3. State Management

    • Choose appropriate patterns
    • Keep state local when possible
    • Use immutable state
    • Handle state changes properly
  4. Animation

    • Use appropriate animations
    • Keep animations smooth
    • Consider performance
    • Follow platform guidelines
  5. Forms

    • Validate inputs properly
    • Provide clear feedback
    • Handle errors gracefully
    • Follow accessibility guidelines

Conclusion

Remember these key points:

  1. Use composition patterns
  2. Implement responsive layouts
  3. Manage state effectively
  4. Add meaningful animations
  5. Handle forms properly

By following these patterns, you can:

  • Build reusable components
  • Improve code maintainability
  • Enhance user experience
  • Reduce development time

Keep building beautiful Flutter applications!