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.