DEV Community

Cover image for Navegação com Flutter
Henrique
Henrique

Posted on

Navegação com Flutter

Saudações!

A Navegação é uma parte essencial na maioria dos apps. Aqui vou abordar o que aprendi ao longo do desenvolvimento de alguns apps.

Introdução

Em aplicativos, a navegação é feita em Stack, isto é, pilha.
pilha
Por exemplo, temos uma tela chamada Home, a qual nos pode levar à próxima tela, Configurações, formando uma pilha. E esta, pode levar para Configurações de conta. Em cada tela nós podemos adicionar ou retirar uma tela da pilha.

Navegação Básica

Sabendo que ela acontece em Stack, vamos ver como adicionar ou remover telas dessa pilha de navegação.

Para começar a fazer uma navegação você vai precisar de, pelo menos:

  1. a tela inicial;
  2. de uma ação para enviar o usuário à próxima tela;
  3. a tela para a qual vamos direcionar o usuário.

Feito isso, nosso evento onPress ou onTap, precisa fazer uma ação sobre o Navigator.

Navigator.of(context).push(
  MaterialPageRoute(
    builder: (_) => NewPageScreen()
  ),
);
Enter fullscreen mode Exit fullscreen mode

Você precisa usar o Navigator (Navegador) do contexto, por isso Navigator.of(context). Ele possui os métodos necessários para controlar a pilha, alguns deles são: push, pushNamed, pop. Nesse caso usamos o push, que precisa receber um Route. Nós poderemos criar depois o nosso próprio Route, mas o Material nos dá o MaterialPageRoute, que recebe um parâmetro nomeado builder, onde podemos simplesmente retornar a página destino.

Transferir dados entre telas

Pode ser que navegar entre telas não nos seja suficiente, mas podemos precisar enviar dados de uma tela para outra.

É possível fazer isso através dos construtores. Desse modo, ao invés de retornarmos a tela sem parâmetros NewScreen(), nós podemos adicionar uma informação no seu construtor.

  • Definição do construtor
class NewScreen extends StatelessWidget {
    const NewScreen(this.title);

    final String title;

// [...]
Enter fullscreen mode Exit fullscreen mode
  • Ao redirecionar para a tela NewScreen
Navigator.of(context).push(
  MaterialPageRoute(
        builder: (_) => NewScreen('Estou aprendendo Flutter')
  ),
);
Enter fullscreen mode Exit fullscreen mode

Retirar da pilha

Para retirar uma tela da pilha, fazendo uma ação de voltar, ao invés de push, basta usar o método pop, que vai retirar a tela atual da pilha. Há também outras funções como popAndPushNamed, popUntil, pushNamedAndRemoveUntil, pushReplacement, entre outros. Você pode explorar mais através da documentação oficial

Rotas nomeadas

Agora vamos falar das rotas nomeadas. Espera, já conseguimos resolver nossos problemas. Por que alguém precisaria de rotas nomeadas? Bom, depende do seu caso, mas para muitas aplicações rotas nomeadas podem lhe dar a vantagem de não ter código duplicado. Se você quisesse navegar para a mesma tela em diferentes partes do seu app, do modo como fizemos até agora, você teria que ter repetidas vezes NewScreen('Estou aprendendo Flutter').

Em nosso arquivo main.dart, temos o MaterialApp, com alguns parametros. Agora nós vamos introduzir um parâmetro chamado routes. Veja o exemplo:

MaterialApp(
    // [...]
    routes: { 
      '/new-screen': (context) => NewScreen(), 
      '/blog': (context) => BlogScreen(), 
    }
);
Enter fullscreen mode Exit fullscreen mode

O que o routes faz é relacionar os nomes das rotas, com os Widgets que as representam. Já é possível entender isso ao ver o tipo de valor que routes deve receber: Map<String, Widget Function(BuildContext)>, que é exatamente isso, uma String que representa um builder de Widget.

Com as rotas nomeadas, o que é considerado a rota inicial é sempre a rota /(barra). Quem vem da programação para web já deve esperar por isso. Nós podemos alterar essa rota inicial adicionando mais um parâmetro no MaterialApp, como se segue:

initialRoute: '/blog',
Enter fullscreen mode Exit fullscreen mode

Precisamos usar um método diferente para o Navigator sempre que quisermos fazer uma navegação utilizando essas rotas nomeadas.

Navigator.of(context).pushNamed(
   '/new-screen',
);
Enter fullscreen mode Exit fullscreen mode

Transferir dados entre rotas nomeadas

A forma mais simples de resolver esse problema é, ao navegar para a tela, enviar argumentos. Eles devem ser do tipo Object, que representa quaisquer tipos; podemos inclusive criar uma classe para encapsular esses argumentos, para maior organização.

Navigator.of(context).pushNamed(
   '/new-screen',
   arguments: 'Flutter is awesome',
);
Enter fullscreen mode Exit fullscreen mode

No exemplo, enviamos uma String, um simples texto. Agora para utilizar esse valor podemos, na tela que representa /new-screen, isto é, NewScreen, vamos usar o ModalRoute:

class NewScreen extends StatelessWidget {
    const NewScreen();

    @override
    Widget build(BuildContext context) {
        String title = ModalRoute.of(context).settings.arguments;

        return Container(
            child: Text(title);
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Geração de rota

Adicionar um evento para geração de rota pode ser muito útil. Vamos introduzir mais um valor para o MaterialApp, o onGenerateRoute. Ele vai servir como um meio de administrar as rotas de forma mais isolada, permitindo fazer alguma operação antes de fazer o push na próxima tela, por exemplo.

onGenerateRoute: (RouteSettings settings) {
   if (settings.name == '/categories') {
      return CategoriesScreen(categoriesRepository);
   }
}
Enter fullscreen mode Exit fullscreen mode

O settings.name representa o nome da rota, como pode ver. No exemplo, estamos passando um repository para dentro da tela CategoriesScreen.

Importante mencionar que quando usamos onGenerateRoute, ele faz o papel do routes que adicionamos antes ao MaterialApp; então você poderia removê-lo para usar a geração de rotas.

Antes de mostrar uma implementação real usando onGenerateRoute, gostaria de mencionar o onUnknownRoute.

onUnknownRoute: (RouteSettings settings) =>
   return MaterialPageRoute(
      builder: (_) => UnknownPageScreen()
   );
Enter fullscreen mode Exit fullscreen mode

É uma forma fácil de mostrarmos uma rota desconhecida. Ele será disparado caso o onGenerateRoute falhe em retornar o Route.

Caso de uso

routes.dart

  // o defaultBuilder serve para as rotas que nao precisam de argumentos
  static Map<String, WidgetBuilder> defaultBuilder = <String, WidgetBuilder>{
    welcome: (BuildContext context) => WelcomeScreen(),
    login: (BuildContext context) => LoginScreen(),
  };

  static Route<dynamic> routeFactory(RouteSettings settings) {
    switch (settings.name) {
      case AppRoutes.signUp:
        return MaterialPageRoute(
          builder: (context) {
            return SignUpScreen(
              userRepository: userRepository,
            );
          },
      settings: settings,
        );

     // [...]

      default:
        return MaterialPageRoute(builder: defaultBuilder[settings.name]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Seguindo esse método, para ir de uma tela para outra basta fazer o pushNamed. Ele vai retornar o MaterialPageRoute que constrói o SignUpScreen passando todos os dados necessários para o construtor.

Navigator.of(context).pushNamed(AppRoutes.signUp);
Enter fullscreen mode Exit fullscreen mode

Se por acaso o switch não incluir alguma rota, com o exemplo do welcome e o login, então usamos um builder padrão, defaultBuilder, para construir a tela.

Utilizando esse padrão de rotas nomeadas e geradas podemos, do mesmo modo, enviar dados através dos arguments. Ou, para muitos casos, bastará termos um repositório que controle o estado de um fluxo, assim a tela para a qual fomos direcionados já terá acesso.

Transição animada

Fazer uma transição de telas personalizada pode dar uma ótima experiência ao usuário. Aperfeiçoando o arquivo de rotas, podemos facilmente adicionar essas animações.

Em Flutter as transições de telas são feitas pelo Route, que no nosso caso é o MaterialPageRoute. Nós podemos criar o nosso próprio Route e sobrescrever a transição padrão. Segue um exemplo:

static Route animatedPageRoute(
      {WidgetBuilder builder, RouteSettings settings}) {
    return PageRouteBuilder(
      settings: settings,
      pageBuilder: (context, animation, secondaryAnimation) => builder(context),
      transitionDuration: Duration(milliseconds: 200),
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        return FadeTransition(
          opacity: animation,
          child: child,
        );
      },
    );
  }
Enter fullscreen mode Exit fullscreen mode

PageRouteBuilder é o que precisamos retornar dessa função. Com seus parâmetros podemos criar a animação que quisermos.

O transitionsBuilder faz a construção da animação. Nesse contexto, nós precisamos retornar uma Transition, entre elas:

  • SlideTransition
  • FadeTransition
  • ScaleTransition
  • RotationTransition
  • SizeTransition

Para mais informações sobre as animações de transição, acesse a documentação oficial.

Este é um resumo do que tenho aprendido com os projetos que pude trabalhar até o momento. Espero que lhe seja útil. Obrigado!

Top comments (0)