Navegação com Flutter
Tags: flutter, navegacao, rotas, pageroutebuilder
Published: 2021-02-05 13:56:13

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](https://dev-to-uploads.s3.amazonaws.com/i/uw9autlfv3u1rwryazh1.png)
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](https://api.flutter.dev/flutter/widgets/Navigator-class.html).

```dart
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (_) => NewPageScreen()
  ),
);
```

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](https://api.flutter.dev/flutter/material/material-library.html) nos dá o *[MaterialPageRoute](https://api.flutter.dev/flutter/material/MaterialPageRoute-class.html)*, 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

```dart
class NewScreen extends StatelessWidget {
	const NewScreen(this.title);

	final String title;

// [...]
```

- Ao redirecionar para a tela *NewScreen*

```dart
Navigator.of(context).push(
  MaterialPageRoute(
		builder: (_) => NewScreen('Estou aprendendo Flutter')
  ),
);
```

### 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](https://api.flutter.dev/flutter/widgets/Navigator-class.html) 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:

```dart
MaterialApp(
	// [...]
	routes: { 
	  '/new-screen': (context) => NewScreen(), 
	  '/blog': (context) => BlogScreen(), 
	}
);
```

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:

```dart
initialRoute: '/blog',
```

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

```dart
Navigator.of(context).pushNamed(
   '/new-screen',
);
```

### 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.

```dart
Navigator.of(context).pushNamed(
   '/new-screen',
   arguments: 'Flutter is awesome',
);
```

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*:

```dart
class NewScreen extends StatelessWidget {
	const NewScreen();

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

		return Container(
			child: Text(title);
		);
	}
}
```

### 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.

```dart
onGenerateRoute: (RouteSettings settings) {
   if (settings.name == '/categories') {
      return CategoriesScreen(categoriesRepository);
   }
}
```

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`. 

```dart
onUnknownRoute: (RouteSettings settings) =>
   return MaterialPageRoute(
      builder: (_) => UnknownPageScreen()
   );
```

É 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

```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]);
    }
}
```

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.

```dart
Navigator.of(context).pushNamed(AppRoutes.signUp);
```

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:

```dart
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,
        );
      },
    );
  }
```

[PageRouteBuilder](https://api.flutter.dev/flutter/widgets/PageRouteBuilder-class.html) é 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](https://flutter.dev/docs/cookbook/animation/page-route-animation) oficial.

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