5 dicas de desempenho do Flutter que farão seu aplicativo voar
5 dicas de desempenho do Flutter que farão seu aplicativo voar
Sejamos honestos: todo aplicativo Flutter parece incrivelmente fluido na primeira vez que você o executa flutter run. Projeto novo, widgets vazios, sem chamadas de rede — tudo perfeito. Mas aí a realidade bate à porta. Você adiciona algumas chamadas de API, um widget ListViewcom 500 itens, talvez uma ou duas animações… e de repente sua “suavidade” parece mais com manteiga congelada recém-saída da geladeira .
As animações começam a travar como se estivessem rodando no Windows 98, a rolagem fica mais travada que a mão de uma criança depois de comer doce, e aquele engenheiro de controle de qualidade começa a enviar relatórios de bugs com capturas de tela que parecem de filme de terror. Enquanto isso, os usuários deixam avaliações de 1 estrela dizendo: “aplicativo lento, por favor, corrijam”.
A boa notícia? Seu aplicativo não precisa parecer o Internet Explorer em uma conexão discada. Com os truques certos, você pode fazê-lo funcionar perfeitamente — de forma fluida, rápida e suave novamente.
1. Pare de retornar widgets de métodos
Em nome de um “código limpo”, muitas vezes ocultamos elementos da interface do usuário dentro de métodos auxiliares — `map`, `filter`, ` buildButton()filter` getCard(). makeHeader()Isso parece organizado no editor, mas, internamente, o Flutter não reutiliza esses widgets. Cada chamada cria um widget totalmente novo, e o framework precisa reconstruir e reorganizar o layout mais do que o necessário. Em outras palavras, tornamos o código mais limpo para nós mesmos, mas mais pesado para o Flutter.
Problema — Retornando widgets de métodos
class HomePage extends StatelessWidget {
@override
Widget build (BuildContext context) {
return Column (
children : [
buildButton ( "Login" ),
buildButton ( "Register" ),
],
);
}
Widget buildButton (String text) {
return ElevatedButton (
onPressed : () {},
child : Text (text),
);
}
}
Aqui, a cada build()execução, o Flutter cria novos botões do zero — mesmo quando nada mudou. A fluidez vai se perdendo aos poucos.
Solução: Extrair em widgets
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: const [
ActionButton(text: "Login" ) ),
ActionButton(text: "Register" ) ),
],
);
}
}
class ActionButton extends StatelessWidget {
final String text;
const ActionButton({required this.text , super.key });
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {},
child: Text(text),
);
}
}
Agora o Flutter pode otimizar e reutilizar widgets, já que eles estão divididos em seus próprios componentes. Sem reconstruções desnecessárias, sem travamentos.
2. Mantenha build()o peso leve
Um erro comum é tratar o Flutter build()como uma gaveta de tranqueiras — simplesmente joga tudo lá dentro. Analisando JSON? Jogue tudo lá. Filtrando listas? Por que não? Formatando datas? Claro, por que não torturar o Flutter um pouco mais? Algumas pessoas até disparam chamadas de rede no Flutter build(), o que é como cozinhar o jantar toda vez que alguém abre a geladeira. Funciona… até o Flutter precisar recompilar, e aí, de repente, seu “aplicativo fluido” parece estar arrastando um piano ladeira acima.
Problema: Realizar o trabalho embuild()
class ProductList extends StatelessWidget {
final List<Product> products;
const ProductList({ super.key , required this.products });
@override
Widget build(BuildContext context) {
// Filtrando dentro de build()
final discounted = products.where ( (p) => p.isDiscounted).toList();
return ListView.builder(
itemCount: discounted.length,
itemBuilder: (context, index) {
return Text(discounted[index].name);
},
);
}
}
Cada reconstrução repete a .where()filtragem — o que é um desperdício e causa lentidão se productso processo for grande.
Solução: Pré-calcular externamentebuild()
class ProductList extends StatefulWidget {
final List <Product> products;
const ProductList({ super.key , required this.products });
@override
State<ProductList> createState() => _ProductListState();
}
class _ProductListState extends State <ProductList> {
late List <Product> discounted;
@override
void initState() {
super.initState ();
// Pré-computar uma vez discounted
= widget.products.where((p) => p.isDiscounted).toList();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: discounted.length,
itemBuilder: (context, index) {
return Text(discounted[index].name);
},
);
}
}
Agora, build()apenas organiza os widgets — sem cálculos adicionais. Mais suave, mais rápido e mais fácil de manter.
3. Use Isolates para tarefas pesadas
O Flutter possui uma única thread de interface do usuário e, quando você a sobrecarrega com tarefas pesadas — como analisar um JSON enorme, processar números ou redimensionar imagens — ele trava. O resultado? Seu aplicativo fica mais lento do que uma chamada de vídeo em uma rede Wi-Fi de hotel.
A solução? Delegar tarefas pesadas para `isolates` . Pense neles como a maneira do Flutter de executar o trabalho em outra instância para que sua interface de usuário não fique sobrecarregada.
Problema: Congelamento da interface do usuário durante a análise de JSON
import 'dart:convert' ;
import 'package:flutter/material.dart' ;
import 'package:flutter/services.dart'; show rootBundle; class JsonFreezeExample extends StatefulWidget { const JsonFreezeExample({ super.key }); @override State<JsonFreezeExample> createState() => _JsonFreezeExampleState(); } class _JsonFreezeExampleState extends State<JsonFreezeExample> { String
result = " Toque para
carregar JSON " ;
Future
<void>
loadJson
( ) async { final raw
= await rootBundle.loadString ( '
assets / large_file.json ' )
; // Esta análise é síncrona
e pesada!
final data = jsonDecode(raw);
setState(() {
result = "Carregados ${data.length} itens" ;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text( "JSON Freeze Demo" )),
body: Center(child: Text(result)),
floatingActionButton: FloatingActionButton(
onPressed: loadJson,
child: const Icon(Icons.download),
),
);
}
}
Se large_file.jsonhouver milhares de registros, a interface do usuário congelará completamente durante a análise — sem animações, sem toques, nada até que a análise seja concluída.
Solução: Analisar JSON em um Isolate ( compute)
import 'dart:convert' ;
import 'package:flutter/foundation.dart' ;
import 'package:flutter/material.dart' ;
import 'package:flutter/services.dart' ; show rootBundle;
// Função de nível superior necessária para compute()
dynamic parseJson( String raw) {
return jsonDecode(raw);
}
class JsonIsolateExample extends StatefulWidget {
const JsonIsolateExample({ super.key });
@override State<JsonIsolateExample> createState
() => _JsonIsolateExampleState();
}
class _JsonIsolateExampleState extends State <JsonIsolateExample> {
String result = "Toque para carregar JSON" ;
Future<void> loadJson () async {
final raw = await rootBundle.loadString( 'assets/large_file.json' );
// Delega a análise sintática pesada para outro isolado
final data = await compute(parseJson, raw);
setState(() {
result = "Carregados ${data.length} itens" ;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text( "Demonstração de Isolado JSON" )),
body: Center(child: Text(result)),
floatingActionButton: FloatingActionButton(
onPressed: loadJson,
child: const Icon(Icons.download),
),
);
}
}
4. O Widget Cebola
Todos nós já vimos (ou escrevemos 😅) isso: Container > Padding > Container > SizedBox > Padding > Container. De repente, sua árvore de widgets parece uma cebola — e, assim como as cebolas, faz as pessoas chorarem.
A verdade é que a maioria desses wrappers extras não faz nada além de tornar as reconstruções mais lentas e dificultar a leitura do seu código. O Flutter oferece diversas maneiras de combinar padding, decoration e layout em menos widgets — sua árvore de widgets (e seus colegas de equipe) agradecerão.
Problema: Widget Onion
class BadNesting extends StatelessWidget {
const BadNesting ({ super .key });
@ override
Widget build (BuildContext context) {
return Scaffold (
body : Center (
child : Container ( // desnecessário
color : Colors.white,
child : Padding (
padding : const EdgeInsets. all ( 16 ),
child : Container ( // outro
decoration : BoxDecoration (
color : Colors.blue,
borderRadius : BorderRadius. circular ( 12 ),
),
child : Padding (
padding : const EdgeInsets. all ( 8 ),
child : Text (
"Muitos... wrappers..." ,
style : const TextStyle ( color : Colors.white),
),
),
),
),
),
),
);
}
}
Isso reconstrói várias camadas de widgets que não fazem nada de adicional — o Flutter precisa percorrer e construir todas elas. É como embrulhar um sanduíche em 5 camadas de papel alumínio, 3 caixas, e depois perguntar por que demora tanto para desembrulhar.
Solução: Utilize as propriedades integradas sempre que possível.
class GoodNesting extends StatelessWidget {
const GoodNesting ({ super .key });
@ override
Widget build (BuildContext context) {
return Scaffold (
body : Center (
child : Container (
padding : const EdgeInsets. all ( 16 ), // combine padding here
decoration : BoxDecoration (
color : Colors.blue,
borderRadius : BorderRadius. circular ( 12 ),
),
child : const Text (
"Muito mais limpo ✨" ,
style : TextStyle ( color : Colors.white),
),
),
),
);
}
}
Aqui, você obtém o mesmo resultado , mas com metade da árvore de widgets . Código mais limpo, compilações mais leves, depuração mais fácil.
5. Os Listeners zumbis que você esqueceu de matar
Vazamentos de memória no Flutter geralmente ocorrem quando você se esquece de liberar recursos em controllers, streams ou listeners. Tudo funciona bem no início, mas depois de algumas navegações ou rolagens de tela, seu aplicativo começa a acumular memória como um dragão sob efeito de cafeína. De repente, a rolagem fica lenta, as animações travam e seu aplicativo fecha inesperadamente em celulares mais antigos.
Problema: Ouvintes Zumbis
class BadLeak extends StatefulWidget {
const BadLeak ({ super .key}) ;
@override
State<BadLeak> createState () => _BadLeakState();
}
class _BadLeakState extends State <BadLeak> {
final _controller = PageController();
@override
Widget build (BuildContext context) {
return PageView(
controller: _controller,
children: const [
Text( "Página 1" ),
Text( "Página 2" ),
],
);
}
}
Aqui, nunca _controllerse descarta nada . Saia desta página algumas vezes e parabéns — você tem um vazamento.
Solução:
class GoodLeak extends StatefulWidget {
const GoodLeak ({ super .key}) ;
@override
State<GoodLeak> createState () => _GoodLeakState();
}
class _GoodLeakState extends State <GoodLeak> {
final _controller = PageController();
@override
void dispose () {
_controller.dispose(); // Limpeza
super .dispose();
}
@override
Widget build (BuildContext context) {
return PageView(
controller: _controller,
children: const [
Text( "Página 1" ),
Text( "Página 2" ),
],
);
}
}
Descartar lixo é como lavar a louça — ninguém gosta, mas se você pular essa etapa, a situação fica feia rapidinho.
No fim das contas, são os pequenos detalhes que se acumulam — os listeners esquecidos, os containers aninhados, os build()métodos complexos. Como desenvolvedores, estamos quase programados para ignorá-los (“ah, é só setStatemais um build(), qual o pior que pode acontecer?”).
Mas, assim como louça suja, dias de academia perdidos ou “só mais um episódio” às 2 da manhã, esses pequenos deslizes se acumulam rapidamente. Individualmente inofensivos, mas juntos eles prejudicam o desempenho do seu aplicativo mais rápido do que o Internet Explorer em conexão discada.
Portanto, organize seu código, mantenha-o enxuto e lembre-se: o Flutter é rápido — geralmente somos nós, os desenvolvedores, que o tornamos mais lento.
Se você gostou deste artigo, compartilhe com seus amigos desenvolvedores (especialmente aqueles que ainda usam 5 containers aninhados para padding) e siga-me para mais dicas e truques de Flutter. Até a próxima — que seus frames permaneçam fluidos e suas compilações leves.