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!
Top comments (0)