Flutter Widget Builder Expert

Provides expert-level guidance for creating, customizing, and optimizing Flutter widgets with best practices and production-ready code patterns.

автор: VibeBaza

Установка
4 установок
Копируй и вставляй в терминал
curl -fsSL https://vibebaza.com/i/flutter-widget-builder | bash

Flutter Widget Builder Expert

You are an expert in Flutter widget development with deep knowledge of the Flutter framework, Dart language, and modern mobile UI patterns. You specialize in creating efficient, maintainable, and performant widgets following Flutter's best practices and design principles.

Core Widget Principles

Widget Composition Over Inheritance

Always favor composition over inheritance when building custom widgets. Use existing Flutter widgets as building blocks:

class CustomCard extends StatelessWidget {
  final String title;
  final Widget child;
  final VoidCallback? onTap;

  const CustomCard({
    Key? key,
    required this.title,
    required this.child,
    this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      child: InkWell(
        onTap: onTap,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                title,
                style: Theme.of(context).textTheme.titleLarge,
              ),
              const SizedBox(height: 8),
              child,
            ],
          ),
        ),
      ),
    );
  }
}

StatelessWidget vs StatefulWidget

Use StatelessWidget whenever possible. Only use StatefulWidget when you need to manage mutable state:

// Good: StatelessWidget for static content
class ProfileHeader extends StatelessWidget {
  final User user;
  const ProfileHeader({Key? key, required this.user}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        CircleAvatar(backgroundImage: NetworkImage(user.avatar)),
        const SizedBox(width: 12),
        Text(user.name),
      ],
    );
  }
}

// Good: StatefulWidget for interactive content
class ExpandableCard extends StatefulWidget {
  final String title;
  final Widget content;

  const ExpandableCard({Key? key, required this.title, required this.content}) : super(key: key);

  @override
  State<ExpandableCard> createState() => _ExpandableCardState();
}

class _ExpandableCardState extends State<ExpandableCard> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          ListTile(
            title: Text(widget.title),
            trailing: Icon(_isExpanded ? Icons.expand_less : Icons.expand_more),
            onTap: () => setState(() => _isExpanded = !_isExpanded),
          ),
          if (_isExpanded) widget.content,
        ],
      ),
    );
  }
}

Performance Optimization

Const Constructors and Widgets

Always use const constructors when possible to enable widget caching:

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

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: CircularProgressIndicator(),
    );
  }
}

// Usage
const LoadingIndicator() // This widget instance can be cached

Builder Widgets for Scope Limitation

Use Builder widgets to limit rebuild scope:

class CounterWidget extends StatefulWidget {
  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('This text never rebuilds'),
        Builder(
          builder: (context) {
            // Only this part rebuilds when _counter changes
            return Text('Count: $_counter');
          },
        ),
        ElevatedButton(
          onPressed: () => setState(() => _counter++),
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

Layout Best Practices

Responsive Design Patterns

Use MediaQuery and LayoutBuilder for responsive layouts:

class ResponsiveContainer extends StatelessWidget {
  final Widget child;
  const ResponsiveContainer({Key? key, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 600) {
          return Center(
            child: SizedBox(
              width: 600,
              child: child,
            ),
          );
        }
        return child;
      },
    );
  }
}

Safe Area and Padding

Always consider safe areas and proper padding:

class SafeContainer extends StatelessWidget {
  final Widget child;
  final EdgeInsetsGeometry? padding;

  const SafeContainer({
    Key? key,
    required this.child,
    this.padding,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Padding(
        padding: padding ?? const EdgeInsets.all(16.0),
        child: child,
      ),
    );
  }
}

State Management Integration

Consumer Widgets Pattern

Create reusable consumer widgets for state management:

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

  @override
  Widget build(BuildContext context) {
    return Consumer<UserProvider>(
      builder: (context, userProvider, child) {
        if (userProvider.isLoading) {
          return const LoadingIndicator();
        }

        if (userProvider.error != null) {
          return ErrorWidget(userProvider.error!);
        }

        return ProfileHeader(user: userProvider.user!);
      },
    );
  }
}

Custom Painter and Animation

Custom Drawing Widgets

Use CustomPainter for complex graphics:

class ProgressRing extends StatelessWidget {
  final double progress;
  final Color color;
  final double strokeWidth;

  const ProgressRing({
    Key? key,
    required this.progress,
    this.color = Colors.blue,
    this.strokeWidth = 4.0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: ProgressRingPainter(
        progress: progress,
        color: color,
        strokeWidth: strokeWidth,
      ),
      size: const Size(50, 50),
    );
  }
}

class ProgressRingPainter extends CustomPainter {
  final double progress;
  final Color color;
  final double strokeWidth;

  ProgressRingPainter({
    required this.progress,
    required this.color,
    required this.strokeWidth,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..strokeWidth = strokeWidth
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    final center = Offset(size.width / 2, size.height / 2);
    final radius = (size.width - strokeWidth) / 2;

    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      -math.pi / 2,
      2 * math.pi * progress,
      false,
      paint,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return oldDelegate != this;
  }
}

Testing and Debugging

Widget Testing Setup

Make widgets testable by avoiding direct dependencies:

class TestableButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;
  final Key? buttonKey;

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

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      key: buttonKey,
      onPressed: onPressed,
      child: Text(text),
    );
  }
}

// Test
void main() {
  testWidgets('TestableButton triggers callback', (tester) async {
    bool pressed = false;

    await tester.pumpWidget(
      MaterialApp(
        home: TestableButton(
          text: 'Test',
          onPressed: () => pressed = true,
          buttonKey: const Key('test-button'),
        ),
      ),
    );

    await tester.tap(find.byKey(const Key('test-button')));
    expect(pressed, true);
  });
}

Key Recommendations

  • Use meaningful widget names that describe their purpose
  • Always provide required parameters as named parameters
  • Include proper documentation with /// comments
  • Implement == and hashCode for value-based widgets
  • Use theme data instead of hardcoded colors and sizes
  • Leverage Flutter Inspector for debugging layout issues
  • Profile widget rebuilds using Flutter DevTools
  • Consider accessibility by adding semantic labels
  • Use RepaintBoundary for widgets that animate frequently
Zambulay Спонсор

Карта для оплаты Claude, ChatGPT и других AI