Se quiser pular direto para o problema/solução pode seguir para a sessão “O que está rolando?”
Um pouco de contexto
Há alguns meses, participei de um projeto onde enfrentei um problema interessante: as telas eram sempre redesenhadas ao navegar, incluindo a abertura de Bottom Sheets, Dialogs, etc.
Acabamos por seguir uma abordagem “inusitada”, no mínimo, para contornar o problema. Perdemos dias pesquisando e tentando entender o que estava acontecendo, mas não encontramos nenhuma solução satisfatória.
O tempo passou, e eu continuei com minhas atividades, até que, em outro projeto, me deparei com o mesmo problema enquanto tentava atualizar o Flutter da versão 3.16.9 para a 3.19.6. Foi então que percebi que o problema poderia estar no Flutter e não na codebase em si.
“Google, meu amigo, venha cá por favor”
Com uma ideia mais clara da origem do problema, consegui realizar uma pesquisa mais assertiva e encontrei alguns insights que vou compartilhar aqui neste artigo.
O primeiro item que encontrei foi esta issue no repositório do Flutter.
Resumindo a thread da issue: várias pessoas estavam enfrentando problemas semelhantes ao atualizar para a versão 3.19.X do Flutter. Em um dos comentários, foi compartilhado um snippet de código para simular o problema, mas vou trazer um exemplo mais detalhado adiante.
Continuando a pesquisa, acabei esbarrando em um fórum japonês com uma explicação mais profunda sobre o problema. Foi através dele que consegui resolver o problema. Mas você pode estar se perguntando:
O que está rolando?
Devido a algumas alterações mergeadas nesse PR, o problema começou a ocorrer.
Mais especificamente essa alteração no arquivo packages/flutter/lib/src/widgets/routes.dart
@override void didChangeNext(Route<dynamic>? nextRoute) { super.didChangeNext(nextRoute); changedInternalState(); } @override void didPopNext(Route<dynamic> nextRoute) { super.didPopNext(nextRoute); changedInternalState(); }
De forma resumida, sempre que uma navegação é feita, o método changedInternalState
é chamado. Ele tem ligação direta com o ModalRoute
. E o que é isso? Eu te explico em seguida.
Imagine o seguinte cenário:
Se da tela /second
você navegar para outra, abrir um dialog ou bottom sheet, a segunda tela será redesenhada. Isso não acontecia antes da versão 3.19.
Veja o exemplo abaixo:
Agora veja quantas vezes as telas foram reconstruídas:
flutter: BUILD FIRST PAGE
flutter: BUILD SECOND PAGE
flutter: BUILD THIRD PAGE
flutter: BUILD SECOND PAGE
flutter: BUILD MODAL BOTTOM SHEET
flutter: BUILD THIRD PAGE
flutter: BUILD THIRD PAGE
flutter: BUILD SECOND PAGE
Isso pode ter vários efeitos colaterais, principalmente ligados a desempenho, até consumo de recursos desnecessários, como múltiplas chamadas a uma API, por exemplo.
Tudo isso acontece devido ao uso do ModalRoute para obter argumentos vindos da navegação. A primeira rota está declarada como constante nos routes que configuramos acima, o que evita esse comportamento. No entanto, para todas as rotas que estiverem na pilha de navegação e não forem constantes, o problema ocorrerá.
Como resolver? Ou não. Hehehe
No fórum japonês mencionado anteriormente, há uma implementação paliativa de um MyModalRoute
. Funciona, já testei.
class MyModalRoute { static ModalRoute<dynamic>? of(BuildContext context) { ModalRoute<dynamic>? route; context.visitAncestorElements((element) { if(element.widget.runtimeType.toString() == '_ModalScopeStatus') { dynamic widget = element.widget; route = widget.route as ModalRoute; return false; } return true; }); return route; }
}
Ao alterar as rotas para usar essa nova implementação de MyModalRoute
, o problema é contornado.
final args = MyModalRoute.of(context)!.settings.arguments; return SecondPage( args: args,
);
No entanto, como alguns podem imaginar, essa é uma solução que traz para nossas mãos a responsabilidade de manutenção no futuro. Afinal, o ModalRoute
é parte da SDK do Flutter, e agora temos nosso próprio.
A melhor solução que encontrei foi a seguinte: utilizar o onGenerateRoute
no lugar do routes
do MaterialApp
.
Diferente do routes
, o onGenerateRoute
é uma função que retorna uma Route
, não somente um Widget
como é padrão do routes
.
A solução para o problema do exemplo acima seria:
onGenerateRoute: (settings) { switch (settings.name) { case '/': return MaterialPageRoute( settings: settings, builder: (c) => const FirstPage(), ); case '/second': return MaterialPageRoute( builder: (context) { final args = settings.arguments; return SecondPage(args: args); }, settings: settings, );
Assim, o problema não acontece mais, e continuamos utilizando ferramentas nativas da SDK do Flutter.
Espero ter ajudado alguém que esteja quebrando a cabeça com esse problema.
Vlw galerinha!
Discover more from Coursity
Subscribe to get the latest posts sent to your email.