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

gguedes

Gustavo Guedes

Posted on August 7, 2024

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!

💖 💪 🙅 🚩
gguedes
Gustavo Guedes

Posted on August 7, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related