5 mixins que tornaram meu código Flutter 10 vezes mais limpo.

Aqui está a tradução para português brasileiro, mantendo o tom descontraído e técnico:

“Se você já usou o SingleTickerProviderStateMixin para animar algo no Flutter, adivinhe só? Você já usou um mixin — parabéns, você está em um relacionamento que nem sabia que existia.

Os mixins são como agentes secretos no seu código: invisíveis, eficientes e silenciosamente fazendo todo o trabalho pesado nos bastidores. Eles permitem que você reutilize lógica em várias classes sem o drama de cadeias de herança profundas. Isso significa menos bugs, menos código repetitivo (boilerplate) e widgets que realmente trazem alegria.

Vamos mergulhar nisso.

1. SingleAnimationMixin — Configuração rápida para um único AnimationController.

As animações no Flutter são poderosas — mas configurar um AnimationController toda santa vez? Não é tão divertido assim.

É aí que entra este mixin prático. Com apenas algumas linhas, você pode reutilizar uma configuração de animação repetitiva e totalmente configurável em qualquer widget — sem boilerplate, sem drama.

mixin SingleRepeatAnimationMixin<T extends StatefulWidget> on State<T>, SingleTickerProviderStateMixin {
  late AnimationController animationController;
  Duration get animationDuration => const Duration(seconds: 1);

  bool get repeatInReverse => true;

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      vsync: this,
      duration: animationDuration,
    )..repeat(reverse: repeatInReverse);
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }
}

Utilização:

class ExampleAnimation extends StatefulWidget {
  @override
  State<ExampleAnimation> createState() => _ExampleAnimationState();
}

class _ExampleAnimationState extends State<ExampleAnimation> with SingleRepeatAnimationMixin {
  @override
  Duration get animationDuration => const Duration(milliseconds: 800);

  @override
  bool get repeatInReverse => false;

  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: animationController,
      child: Container(width: 100, height: 100, color: Colors.purple),
    );
  }
}

2. FormFieldValidationMixin — Pare de reescrever validadores básicos.

Cansado de escrever aquele mesmo velho if (value == null || value.isEmpty) de sempre? Este mixin oferece validadores prontos para o uso, no estilo plug-and-play, para os formulários do dia a dia.

mixin FormFieldValidationMixin {
  String? isRequired(String? value, [String field = "This field"]) {
    if (value == null || value.trim().isEmpty) return "$field is required.";
    return null;
  }

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

Utilização:

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

class _LoginFormState extends State<LoginForm> with FormFieldValidationMixin {
  final _formKey = GlobalKey<FormState>();
  final _email = TextEditingController();
  final _password = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _email,
            validator: isEmail,
          ),
          TextFormField(
            controller: _password,
            validator: (v) => isRequired(v, "Password"),
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
           
              }
            },
            child: Text("Login"),
          )
        ],
      ),
    );
  }
}

3. DelayedInitMixin — Execute código após o primeiro frame.

Precisa disparar algo somente depois que o seu widget terminar de ser construído — como saltos de rolagem (scroll jumps), animações ou a exibição de um diálogo?

É aí que o DelayedInitMixin entra.

Ele permite que você execute código com segurança após o primeiro frame ser renderizado — sem gambiarras, sem Future.delayed, apenas lógica limpa de pós-layout.

mixin DelayedInitMixin<T extends StatefulWidget> on State<T> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => afterFirstLayout());
  }

  void afterFirstLayout();
}

Utilização:

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with DelayedInitMixin {
  @override
  void afterFirstLayout() {
    print("Widget has rendered!");
  }

  @override
  Widget build(BuildContext context) => Container();
}

4: RebuildCounterMixin — Capture reconstruções indesejadas como um profissional.

Já se perguntou se o seu widget está sendo reconstruído (rebuilding) mais vezes do que deveria?

Este mixin ajuda você a rastrear as reconstruções em tempo real — perfeito para depuração de performance, otimização de widgets ou apenas para satisfazer sua curiosidade.

mixin RebuildMixin<T extends StatefulWidget> on State<T> {
  int _rebuilds = 0;

  @override
  Widget build(BuildContext context) {
    _rebuilds++;
    debugPrint('${widget.runtimeType} rebuilt $_rebuilds times');
    return buildWithCount(context, _rebuilds);
  }

  Widget buildWithCount(BuildContext context, int count);
}

Utilização:

class CounterExample extends StatefulWidget {
  const CounterExample({super.key});

  @override
  State<CounterExample> createState() => _CounterExampleState();
}

class _CounterExampleState extends State<CounterExample>
    with RebuildMixin {
  int count = 0;

  @override
  Widget buildWithCount(BuildContext context, int rebuilds) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('Rebuilds: $rebuilds'),
            ElevatedButton(
              onPressed: () => setState(() => count++),
              child: Text('Tap $count'),
            ),
          ],
        ),
      ),
    );
  }
}

5. RenderAwareMixin — Saiba o tamanho e a posição do seu Widget.

Quer obter o tamanho de um widget ou sua posição (offset) na tela após ele ter sido renderizado?

Este mixin oferece acesso limpo às propriedades Size e Offset — sem gambiarras de layout ou malabarismos complicados com GlobalKey.

mixin RenderAwareMixin<T extends StatefulWidget> on State<T> {
  final renderKey = GlobalKey();

  Size? get widgetSize {
    final ctx = renderKey.currentContext;
    if (ctx == null) return null;
    final box = ctx.findRenderObject() as RenderBox?;
    return box?.size;
  }

  Offset? get widgetPosition {
    final ctx = renderKey.currentContext;
    if (ctx == null) return null;
    final box = ctx.findRenderObject() as RenderBox?;
    return box?.localToGlobal(Offset.zero);
  }
}

Utilização:

class MeasureBox extends StatefulWidget {
  const MeasureBox({super.key});

  @override
  State<MeasureBox> createState() => _MeasureBoxState();
}

class _MeasureBoxState extends State<MeasureBox> with RenderAwareMixin {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          key: renderKey,
          onTap: () {
            final size = widgetSize;
            final pos = widgetPosition;
            debugPrint("Size: $size");
            debugPrint("Position: $pos");
          },
          child: Container(
            color: Colors.blue,
            width: 120,
            height: 80,
            alignment: Alignment.center,
            child: const Text('Tap Me', style: TextStyle(color: Colors.white)),
          ),
        ),
      ),
    );
  }
}

Os mixins são os superpoderes silenciosos do Flutter — reutilizáveis, elegantes e extremamente subestimados. Adicione alguns ao seu kit de ferramentas e seu código ficará instantaneamente mais limpo, inteligente e muito mais divertido de trabalhar.

Please follow and like us:
error0
fb-share-icon
Tweet 20
fb-share-icon20