DEV Community

Gustavo Guedes
Gustavo Guedes

Posted on

Problemas com rebuilds ao navegar? Acho que posso te ajudar.

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();
  }
Enter fullscreen mode Exit fullscreen mode

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:

routes_config

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:

gif_01

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
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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,
);
Enter fullscreen mode Exit fullscreen mode

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,
      );
Enter fullscreen mode Exit fullscreen mode

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)