Meu nome é Ricardo Lima e serei o instrutor em mais um curso de Flutter. Sou um homem com cabelo Black Power, pele dourada, uso óculos de armação transparente e barba. Durante todo o curso, estarei vestindo uma camisa preta.
Audiodescrição: Ricardo é um homem com cabelo Black Power, pele dourada, usa óculos de armação transparente e barba. Ele veste uma camisa preta.
Sejam muito bem-vindos ao nosso curso de Flutter, GoHowter e DeepLinks (links profundos). Estou ansioso para começar este curso, pois ele é bastante importante no que vamos abordar.
Já ouviram falar de DeepLink? Vamos dar uma olhada no projeto e, se ainda não conhecem, verão quão importante isso é para o mercado.
No meu TechTaste, um agregador de restaurantes, estou usando um dispositivo físico. Quando clico, aparece meu dedo na tela. Vou narrar o que estou fazendo. Na tela inicial, ao rolar para baixo, vemos vários restaurantes. Digamos que eu queira comer sushi hoje; vou clicar no Panda Sushi.
Entrando no Panda Sushi, quero um temaki de salmão, que está com um preço acessível. Vou clicar para entrar.
Nós avançamos em cada tela deste aplicativo, em cada tela do nosso projeto. Mas, se quisermos compartilhar este temaki de salmão especificamente, pensem em outros aplicativos que já usaram. Como faríamos? Provavelmente haveria um botão de compartilhar, mas o mais importante seria a URL.
Neste curso, nosso foco principal é compreender a importância de uma URL que nos direciona diretamente a um prato específico de um restaurante. Quando acessamos essa URL em um navegador, como Google Chrome ou Safari, em um dispositivo móvel, o navegador detecta que não se trata apenas de um site, mas também de um aplicativo instalado. Em vez de abrir o site, ele abrirá o aplicativo diretamente na página do prato específico, não na página inicial.
Esse conceito é semelhante ao que ocorre nas redes sociais, onde podemos acessar um link que nos leva a uma parte específica do aplicativo, em vez de começar do início. Essa tecnologia é conhecida como Deep Link (link profundo). Atualmente, praticamente qualquer aplicativo de qualquer empresa utiliza essa tecnologia para aumentar a quantidade de cliques, engajamento, compras e direcionar as pessoas para a parte específica da aplicação que a URL gerou.
Para implementar isso, utilizaremos o pacote GoRouter
para realizar toda a configuração necessária.
É importante destacar os pré-requisitos para este curso. É essencial que tenhamos concluído a formação em Dart e Flutter, pois elas fornecem uma base sólida para prosseguir.
Este curso é um pouco diferente do que estamos acostumados na plataforma, pois segue o estilo Live Coding (codificação ao vivo). Recomendamos que acompanhemos o desenvolvimento como se estivéssemos lado a lado, mesmo com a distância temporal. Essa abordagem torna o projeto mais prático e envolvente.
Estamos ansiosos para começar. Vamos juntos!
Vamos colocar a mão na massa. Temos um desafio aqui. Agora que conhecemos nosso projeto por apresentação, vamos entender como está o código dele. Então, se eu abrir o meu VS Code, vou dar até um Ctrl B para interagir com o explorador por enquanto, nós vamos analisando arquivo por arquivo.
Você vai ver que a minha main app
está chamando um home
, especificamente na linha 36, está chamando um splash screen
. Vou pressionar Ctrl e vou clicar na splash screen
para abrir o arquivo. A splash screen
é aquela que conhecemos. Posso até mostrar no nosso, não é o emulador, mas talvez eu chame de emulador, certo? Mas é o pareamento do meu dispositivo físico. Mas essa é a nossa home screen
. Nenhum segredo aqui, uma imagem, a logo, o botão de continuar e por aí vai.
Se eu rolar para baixo, você vai ver que tem um método chamado Onboard
, que é o nome do botão, Button Pressed
, ou seja, o que acontece quando esse botão for pressionado. E o que estamos fazendo aqui, da linha 72 a 79? Estamos usando o Navigator
para fazer um Push Replacement
e aparecer uma nova tela. Tudo que aprendemos na nossa formação Flutter (plataforma de desenvolvimento de aplicativos). Ótimo. Essa nova tela é uma home screen
.
A home screen
, já vindo no meu emulador, no meu pareador, ela tem toda a questão de flutuar por categoria, ela tem a questão de pesquisar, tudo está funcionando bem. Mas o que importa é que da home screen
eu posso ir para duas telas, ou para a tela de, deixa eu até tirar a categoria principais, ou para a tela do restaurante, ou para a tela de checkout. Vamos deixar a checkout um pouco mais para frente.
Da tela de restaurante, como é que eu faço para ir? Já voltando para o meu código, eu vou ter mais uma vez, na verdade essa daqui é de Logout
, não vamos olhar para ela necessariamente agora, mas era um Navigator
, não estou escondendo nada, era um Navigator
como todos os outros. Mas vamos ter um Restaurant Widget
que representa, voltando no meu simulador, digamos que ele representa cada widget desse, representando um restaurante. Quando eu clico nele, o que que acontece? Ele me manda para a página do restaurante.
Daí, se eu olhar direitinho aqui, a página do restaurante está sendo chamada, quando eu pressiono, eu estou no meu Restaurant Widget
e ela chama com o Navigator
. Então deu para entender, né? Nós estamos trafegando informação, até porque o touchscreen
, por exemplo, na linha 46, ele tem um parâmetro que recebe como argumento um restaurante, um modelo restaurante que já tem no nosso projeto. Então, nós estamos trafegando essa informação sempre usando o Navigator
.
Uma coisa que eu não mostrei no vídeo de apresentação, na parte de apresentação, melhor dizendo, mas que é bem importante que você saiba, é que você pode adicionar um pedido, vou clicar para adicionar 5 Xtudons aqui, ele aparece na minha mochilinha de Checkout
e quando eu entrar, a informação está aqui. E toda essa informação está sendo trafegada pela minha aplicação.
Só que tem uma grande questão, qual é o nosso desafio? Vamos sempre ter ele em mente. Nós precisamos criar esse conceito de Deep Link (link profundo) na nossa aplicação. Então, quando eu tiver, por exemplo, na página Xtudão, eu quero que eu esteja no Xtudão do Monstro Burger, que é um restaurante. Se eu tiver na página Home
, eu quero estar na página Home
. Se eu tiver na página Checkout
, eu quero estar nessa página também.
Então, no futuro, caso eu disponibilizasse uma URL numa hospedagem da vida e coisa do tipo, se a pessoa escrevesse text.com barra Home, ela cairia na nossa Home
. Se ela escrevesse a mesma coisa, só que com o nome do restaurante, ou melhor, com o ID do restaurante e o ID do pedido, ela cairia no pedido. Só que esse conceito não existe na nossa aplicação ainda. Nossa aplicação lida com chamar uma tela ou outra apenas chamando justamente os nossos widgets, nossos Stateless Stateful Widgets. Não há uma lógica de URL para a navegação, para chegar nas telas. E como nós vamos resolver isso?
Existe uma forma de resolver que você já pode conhecer, que são as rotas nomeadas já nativas da nossa main
, do nosso Material App
. Poderíamos usar rotas nomeadas, só que tem uma questão sobre elas.
Quando estamos utilizando Deep Link (Link Profundo), buscamos garantir a independência e autossuficiência de cada uma das telas. Portanto, quando chegamos a uma tela de um prato, é necessário que todo o aplicativo esteja preparado para receber aquele prato. Ao voltar, o aplicativo não pode ser fechado. Ele deve retornar para o restaurante ao qual aquele prato pertence, e assim por diante.
Realizar isso por meio de rotas nomeadas pode tornar nosso código um pouco mais desorganizado e oneroso. Por isso, a própria equipe de desenvolvimento do Flutter disponibiliza um pacote que podemos instalar para lidar com essa ideia de rotas, principalmente quando queremos torná-las mais próximas de uma URL que conhecemos na web, de forma bem organizada e fechada em funcionalidades, conforme a própria documentação indica. O nome desse pacote é Go Router.
Para instalá-lo, abrirei o terminal com ctrl-j
, e executarei o comando flutter pub add go_router
. Ele será instalado no meu arquivo pubspec.yml
.
Com o Go Router instalado, nosso próximo passo é configurá-lo. Para isso, fecharei todas as abas que não sejam a main.dart
, pois não quero me preocupar com elas agora. Em seguida, abrirei o explorador com ctrl-b
e, dentro da pasta lib
, criarei um novo arquivo chamado router.dart
.
Dentro desse arquivo, criarei uma abstract class (classe abstrata). Por que usar uma classe abstrata? Bem, até funcionaria se fosse uma classe comum, mas como nada da nossa classe será instanciado, serão sempre consultas à nossa classe, não faz sentido gastarmos processamento criando uma instância, ou permitindo que as pessoas que vão usar nosso código criem instâncias. Portanto, a classe abstrata resolve isso bem para nós.
Chamarei essa classe de AppRouter
, pois ela lidará com as coisas relacionadas ao nosso aplicativo e ao roteamento. Em seguida, importarei o Go Router com o comando import 'package:go_router/go_router.dart';
.
Com essa importação disponível, posso criar nosso roteador, que se chamará GoRouter
, e atribuir a ele o nome AppRouter
. Lembrando que esse GoRouter
está vindo do pacote Go Router. Farei com que ele seja estático, para que possa ser acessado sem instanciar a classe.
Ele pede obrigatoriamente um route
, que é uma lista de RouteBase
. Será dentro dessa lista que configuraremos cada tela com uma rota diferente. Por enquanto, deixarei uma lista vazia, apenas indicando o tipo da lista como RouteBase
, por uma questão de boa prática.
Agora, quero trazer isso para minha main.dart
, quero que ela seja capaz de trabalhar com esse GoRouter
que criei. Para isso, no nosso MaterialApp
na linha 33, farei .router
, que só está disponível por causa do nosso Go Router.
Ele espera um GoRouter
que criamos no nosso router.dart
. Portanto, não teremos mais uma home
, isso não será mais definido aqui. Na linha 36, apagarei a home
e farei routerConfig
, que espera o GoRouter
que criamos.
Para chamá-lo, vou fazer app-router
. Ele importa para mim, em algum lugar aqui na linha 3, ele importa o nosso go-router
e, por fim, vou fazer app-router
. Entendido? Vou apenas remover essa splash screen (tela de abertura) da importação, porque já não faz sentido chamá-la aqui, vou apagar, vou salvar e, no meu aplicativo, se eu abrir a minha transmissão, você verá que já ocorreu um pequeno erro aqui, go-exceptions
, know-how for location, barra. Por que essa barra? Se você tem alguma experiência com web, vai lembrar que barra normalmente quer dizer a raiz do projeto, onde tudo começa, onde será pesquisado o início e realmente não tem nenhuma rota como barra configurada no nosso arquivo router.dart
. Para ser bem sincero, na verdade não tem nenhuma rota configurada, certo? Vamos fazer isso agora?
Para fazer isso, uma boa prática que quero aplicar aqui é criar variáveis, ou melhor dizendo, como elas não vão variar, elas são constantes com a informação de cada rota, porque quando eu for chamar isso na tela, não quero correr o risco de cometer um erro de digitação, já que essas rotas são strings (cadeias de caracteres), então é melhor criar já direto aqui um arquivo de constante, eu só fico chamando essas constantes, essas strings.
Quais são as telas que teremos aqui? Vamos começar com aqui, sabemos que deu um problema, certo? Então está static final
, ela não vai mudar, string, route
, ou melhor, root
, melhor dizendo, de raiz, e isso vai ser só uma barra. Quero fazer outras para cada uma das telas, então vou dar um atalho para vocês, alt
, shift
e seta para baixo, o que isso faz? Duplica a linha para mim, então só preciso mudar qual é o nome da variável, vou chamar de splash
, que é a minha primeira tela, e ela vai ser barra splash
.
Por padrão, já vale dizer isso aqui, por padrão, as rotas aqui no go router
, elas não terminam com a barra, elas começam com a barra. Se no futuro você quiser encadear essas rotas, precisa levar isso em consideração, então a nossa splash
fica como barra splash
, não splash
barra ou barra splash
barra, beleza? Alt
, seta para baixo, vou fazer a de home
, home
, alt
, seta para baixo, vou fazer de restaurante, então restaurant
, e a mesma coisa aqui, restaurant
, alt
, seta para baixo, o que mais temos? Temos a tela de pratos, então dish
, ótimo, alt
, seta para baixo, alt
, shift
e seta para baixo, melhor dizendo, porque alt
, seta para baixo só move a linha, certo? Fica aí outro atalho, mas além do dish
, vamos ter a página de checkout
, e tem uma página bem importante, eu já estou chamando de página, mas por enquanto vamos chamar de tela, tem uma tela bem importante que não foi mostrada, porque não tinha nem como ela aparecer, mas que já vou deixar pré-configurada, que é a tela de o que acontece se ele não encontrar nenhuma página, porque pensa comigo, quando a pessoa jogar o domínio do meu site, do meu app no navegador, ela pode escrever alguma coisa errada, então pode cair em uma página que a nossa aplicação não está preparada para mostrar alguma coisa, e nesses casos precisamos ter também uma tela, então alt
, shift
, seta para baixo, essa tela vai se chamar not found
, not found
, e a rota para ela é 404
, em menção ao status 404
do HTTP, que é justamente página não encontrada. Ótimo! Já podemos começar a programar nossas rotas dentro do alt
na linha 12, para fazer isso vou passar para ele um go-alt
, não go-halter
, certo?
Go-halter é o nosso objeto de configuração, um go-alt. O que ele exige de mim é um caminho, um path, que são justamente os padrões que estávamos definindo. Então, se eu quero para root, se eu quero para quando a pessoa não digita nenhum caminho, a página inicial mesmo, eu vou passar root. No entanto, eu tenho que passar também qual é a tela que vai ser construída na hora desse root.
Então, vou fazer um contraspaço para ver todas as minhas possibilidades de parâmetros, e vou ver que ele já está indicando aqui um builder. Vou usar esse builder mais uma vez. Vou usar o contraspaço. Essa é uma dica que eu dou sempre, apesar de que já está aparecendo, sempre que você vê um builder, não custa nada lembrar. A minha dica é: dê um contraspaço, porque ele vai sugerir qual é o callback, qual é a função que você tem que passar, e como ela está estruturada, que é mais interessante, ou melhor, que esse builder exige para funcionar.
Então, eu vou passar, eu não vou usar arrow, eu poderia usar arrow normal, mas eu vou usar, na verdade pode ser arrow sim, você sabe a diferença né? A arrow é quando é uma linha só, que eu posso fazer um retorno direto, e a função normal, com abre e fecha chaves, é quando aí sim, nós precisamos de várias linhas. Como por enquanto eu vou só usar uma linha, eu vou passar arrow sim. E qual é a tela que eu quero criar na raiz? A minha tela de splash. Então vou criar splash screen.
Vou botar uma vírgula, e vamos seguir. Vamos seguir agora, com as telas que não precisa de parâmetros, porque lembra, quando eu entrava num restaurante, eu precisava dizer qual era o restaurante, quando eu entrava num prato, eu precisava dizer qual era o restaurante, qual era o prato, mas as outras, elas podem ser acessadas direto. Vamos configurar elas.
Então eu tenho a splash screen, vou mais uma vez dar um alt shift, setinha para baixo, porque grande parte da configuração não muda. Só que na linha 15, ao invés de root, eu vou botar também splash, porque tanto a root, quanto a barra splash, chegam na splash screen.
Alt shift, setinha para baixo, vou configurar a home. Ela também não precisa de nenhum parâmetro, para ela, então vou passar só uma home screen. Na linha 17, vou ter o path como sendo home, e o resultado do meu builder como sendo home screen.
Alt shift, setinha para baixo, vou colocar um checkout. Checkout também não precisa, ele tem toda a inteligência para anotar o que foi colocado no carrinho direto, ela não precisa ser passada por parâmetro, essa informação. Então vou chamar aqui, checkout, checkout screen.
E por fim, alt shift, setinha para baixo, e vou passar o nosso not found, not found, e o nosso not found cai na nossa tela, not found screen.
Vou fazer o seguinte, eu vou salvar e vamos dar uma olhada como é que se comporta lá a minha aplicação. Vou salvar, e bom, por enquanto nada aconteceu. Eu vou dar um hot reload, e vamos ver se alguma coisa muda. Olha que interessante, ele já mostra aqui para mim, a minha tela de splash, porque, o que aconteceu, lembra que o problema que estava dando é, não tenho nenhuma rota para barra, que é uma forma de dizer página inicial. Agora que definimos root como sendo splash screen, ele vai para splash screen.
Vamos testar, vamos botar outra coisa aqui, vamos botar uma bem nada a ver, que não faria nenhum sentido, que é a checkout. Vou salvar, vou dar um hot reload, vou reiniciar a aplicação, e vamos ver onde é que ele vai parar. Olha, parou na nossa tela de checkout, muito massa, esse caminho é bem interessante de configuração.
Mas tem uma forma mais elegante de nós configurarmos essa nossa root. Vamos fazer isso agora. Passei explicitamente minha root aqui, até com o string, a nossa constante como barra e tal, mas é claro que o Go router tem uma inteligência de saber que a rota inicial é uma rota importante, então seria interessante ter um atributo para configurá-la, e tem. O nosso Go router na linha 16, tem um atributo que configura a tela inicial, o nome dele é initial location. O nosso initial location é apenas a nossa tela que nós queremos que comece, no nosso caso é a splash. Então olha, não preciso mais da minha linha.
A partir de uma configuração por root (raiz), a própria initial location (localização inicial) sabe que, ao chegar em uma barra vazia, ou seja, quando chegar em um caminho que não define nenhum outro caminho, no caminho inicial, ela vai identificar qual é a que corresponde com o splash dentro da lista de router (roteador), que será justamente a da linha 20, e vai direcionar para lá. Portanto, também posso excluir a linha 19, afinal, já está ocorrendo um erro aqui, e o nosso checkout não fazia sentido lá.
Vou salvar e reiniciar a aplicação. É importante fazer essa reinicialização, porque como fizemos alterações na nossa main (principal), ela precisa ser recarregada. E pronto, retornamos para o meu splash que agora está configurado usando o initial location do Go router (Roteador Go).
Ótimo, nosso próximo trabalho será realizar substituições no nosso navigator
. Na própria documentação do Go router (Roteador Go), existe uma seção chamada "migrando do navigation" (migrando da navegação), pois é bastante comum começarmos a desenvolver nossa aplicação Flutter usando o nosso navigator
. Portanto, navigator.push
, navigator.push replacement
, e assim por diante, vamos substituir pelo menos para as telas que já configuramos. A tela splash
chama home
, a appbar
chama checkout
e splash
, e a not found
chama a home
. Vamos configurar tudo isso.
Para fazer isso, vou abrir meu explorador, ou melhor, nem preciso, eu posso vir daqui. Vou dar um control
e clicar na splash screen
. Rolando para baixo na minha splash screen
, nós temos um método que é o que acontece quando eu clico no botão de "bora". Ao invés desse navigator
todo, eu poderia removê-lo, mas vou apenas comentar para termos essa comparação. Vou comentar, selecionei da linha 72 a 79, vou fazer cont
e ponto e vírgula, um atalho aqui no VS Code para comentar o bloco todo de uma vez.
Agora, para chamar a tela via Go router, vou fazer context
, que já estou recebendo, ponto go
. Lembrando que esse go
só existe por causa do pacote Go router. Se você tentasse fazer context.go
sem o pacote instalado, não funcionaria, ele sequer apareceria como sugestão. E vou chamar, qual é a location, qual é o caminho que eu quero seguir? Eu poderia simplesmente fazer aqui, home
, mas sabemos que poderíamos cometer um erro de digitação nesse caso. Então, por isso que criamos nossas constantes. Para acessá-las, vou fazer app router
, importo app router
, ponto, quem eu quero passar aqui, home
. Veja que é apenas a string que está chegando, mas pelo menos garante que eu não vou cometer nenhum erro de digitação aqui. Já vamos testar para ver se funciona e depois fazemos as outras.
Estou com o meu transmissor aberto aqui, e vou clicar em "bora". Vamos lá, foi para a tela inicial, perfeito, para a tela de home
, melhor dizendo.
Próximo passo, na própria tela de home
, eu posso até vir pela router
aqui. Vou remover a nossa importação, que não faz mais sentido, só para não ficar dando warning, na linha 5 da splash screen
. Como já não chamo diretamente pela splash screen
a home screen
, eu posso deletar a importação. Nós vamos fazer isso muito ao longo do nosso curso.
Voltando na router
, eu posso entrar na minha home screen
. Só que na home screen
tem alguns detalhes. Primeiro, no nosso drawer
, no botão "sair". Se eu clico no drawer
, ele abre para mim. Você vai ver que esse espiral vai ser bastante útil para nós mais para frente. Mas eu posso clicar aqui em "sair", e o que acontece? Opa, ele deu um erro aqui, porque agora estamos usando o Go router e não mais o Navigator
.
Vou abrir a home
aqui, ele fechou a home
para mim. Então vou só na minha router
e vou na linha 19 e abro a home
. Você vai ver que o botão de "sair" foi até um erro meu, não deveria ter clicado com o Navigator
ainda no código, mas não estamos mais usando o Navigator
. Nós vamos usar via Go router. Então ele olhou para isso aqui e disse que não faz sentido mais nenhum.
Vamos já resolver essa situação? Na linha 49, faço cont
e ponto e vírgula, e quem é que eu estou chamando? Se eu ver aqui nos comentados ainda, na linha 54, estava chamando a splash screen
, porque se eu desloguei, eu volto para a tela de splash
, que poderia ser uma tela de login também, se fosse o caso. Mas eu vou fazer apenas um context.go
e chamo nosso app router.splash
. Vou salvar, vou dar um reload para me livrar desse hashtag de erro, ela volta para mim. Vou clicar em "ir embora", vou clicar no meu drawer
e vou clicar em "sair". Olha, ele volta, agora é assim para a minha tela de home
.
Outra funcionalidade que posso acessar através da tela inicial é clicar em um restaurante. No entanto, lembre-se de que o restaurante exige um parâmetro, então não estamos lidando com isso por enquanto. Na minha tela inicial, também há um caminho para o checkout. Como será que eu chego nesse checkout? Eu chego por meio da appbar
. Deixe-me procurar minha appbar
aqui. Veja, a appbar
da minha tela inicial vem através de um getappbar
, porque essa appbar
é reutilizada em outras telas. Portanto, criamos uma função externa a todas essas telas que simplesmente gera uma appbar
para nós.
Se eu pressionar ctrl
e clicar em getappbar
, vou para essa função e vamos notar que, apesar de ter toda a questão dos beds
aqui para mostrar a notificação, o que importa para nós é esse iconbutton
. E esse iconbutton
chama mais uma vez, através de um navigator
, a nossa tela de checkout. Vamos fazer a mesma coisa, vou selecionar da linha 27 até 34, ctrl
e ponto e vírgula para comentar e vou fazer um context.go
. Chamo nosso apphalter
e para onde queremos ir? Para a tela de checkout, passo o checkout, vou salvar.
Se eu abrir o terminal e clicar agora, fui para a tela de checkout. A tela de checkout não tem um botão de voltar ainda. Por que isso está acontecendo? Porque antes a tela de checkout era chamada com um push
. Note que na linha 30 era um push
, então criava aquele stack de telas e quando tem o stack de telas, a nossa appbar
automaticamente já tem a inteligência de criar um botão de voltar para a tela inicial. O go
, não, a tela anterior, melhor dizendo. O go
não faz isso, o go
não permite a appbar
fazer isso, então vamos ter que configurar lá na tela de checkout também.
Para fazer isso, posso vir já no meu halter
, deixa eu só tirar os warnings da appbar
na linha 5, não estamos mais chamando o checkout diretamente, posso fechar essa appbar
e na tela inicial também posso fechar, ou melhor, posso tirar na linha 11 a splash screen
, ele some com esses warnings, posso fechar a tela inicial, posso fechar a splash screen
e na nossa halter
, se eu for em checkout screen
, você vai ver que a appbar
não é essa appbar
reciclada de várias telas, porque tem um comportamento diferente especificamente no nosso checkout, então ela é uma appbar
simplesmente criada com o título "sacola" e com o botão de limpar.
Se eu mostrar aqui, nós temos um botão de limpar, basta criarmos no leading
um botão que faça esse trabalho para nós. Então na linha 18, vou dentro da appbar
fazer um leading
, vou chamar icon button
, quando pressionar vou fazer alguma coisa, mas o que importa é que aqui em icon
, vou passar icons.arrowback
. Isso, coloco uma vírgula para ele ficar bonitinho, ele tirou, o linter na nova atualização do Flutter (Ferramenta de desenvolvimento de interface de usuário) está tirando as vírgulas quando não tem uma quebra de página, isso me deixa pessoalmente desagradado, mas quando eu colocar um comportamento aqui no unprecedented
, ele vai aceitar a minha vírgula, mas esse comportamento é um context.goal
, e esse goal
vai ser apphalter.home
, sempre do checkout, agora eu volto para a tela inicial, você vai ver que apareceu a minha setinha e eu posso voltar.
Há uma última tela em que podemos fazer essa refatoração, porque o que estamos fazendo é uma refatoração. Para a pessoa usuária, o comportamento da aplicação continua o mesmo, mas para implementar uma nova funcionalidade que é o deep link (link profundo), estamos mudando como o código funciona. E sempre que a funcionalidade para a pessoa usuária se mantém, mas melhoramos o código, o nome disso é uma refatoração.
Na linha 21 do nosso router
, vou entrar na minha not found screen
, e vou criar um ícone bem parecido com o que eu criei lá no checkout. Eu não vou voltar lá, vou fazer na mão, vou criar o leading
, na verdade o leading
já está aqui, então só vou substituir na linha 16, comento o meu navigator push replacement
, e chamo context.goal
. Para onde eu volto?
Retorno para minha tela inicial, que é a tela de home (início), para ser mais preciso, não é a splash screen (tela de apresentação), apphalter.home
. Está aparecendo este aviso aqui, porque não estou chamando diretamente com o navigator (navegador), então não posso remover da importação. Agora, nós implementamos toda a lógica de transição de telas para as telas que não dependem de parâmetros. Chegou o momento de fazer isso para as duas telas que estão faltando, a de restaurante e a de pratos. Vamos dar uma olhada em como elas se comportam, e então começamos a implementar essa lógica.
Na minha home screen (tela inicial), vou pelo router (roteador), clicar com o conteúdo na home screen (tela inicial), abro meu arquivo da home screen (tela inicial). Lembrando que os meus restaurantes são exibidos na home screen (tela inicial), até mostrei a home screen (tela inicial) para você se lembrar, cada um desses widgets aqui é um restaurant widget (widget de restaurante). Afinal, não criamos cada um desses layouts bonitos para cada restaurante sem motivo, não faria sentido, poderia ter centenas de restaurantes aqui. Então, temos o restaurant widget (widget de restaurante), o restaurant widget (widget de restaurante) recebe um restaurante, que é um objeto do tipo restaurant (restaurante), que é nosso model (modelo).
Para dar só uma olhada aqui no model (modelo), vou dar um ctrl e clicar em restaurant (restaurante). É um model (modelo) comum, ele tem um id, tem um image path (caminho da imagem), tem um name (nome), description (descrição), stars (estrelas), tem uma lista de categorias que aquele restaurante agrega, é agregado, para ser mais preciso, e tem uma lista de pratos. Ótimo! Esse é o nosso modelo de restaurante, mas eu preciso passar essa informação. Vamos ver como ela está sendo passada atualmente.
Deixe-me apenas tomar uma água, eu me animo e esqueço de beber água. Beba água você também. Mas, restaurant widget (widget de restaurante), vou clicar com o ctrl na linha 128, nós vamos para esse widget (componente), e nós vamos abrir o restaurant (restaurante). Não, vamos ver o método onRestaurantPressed (ao pressionar o restaurante), ou seja, quando eu clico no restaurant (restaurante), na linha 42, começando na linha 42, você vai ver que é um navigator (navegador), com um push (empurrar), e que ele chama o materialPageRouter (roteador de página de material), passando um restaurantScreen (tela de restaurante), e esse restaurantScreen (tela de restaurante) passa um restaurant (restaurante). Não tem segredo aqui. Como é que vamos fazer isso, considerando que agora estamos usando o Google?
O método Go
possui um parâmetro chamado extras
, que serve para passarmos um objeto. No nosso caso, um restaurante é um objeto, então vamos fazer isso. Aproveitando que já estou no Restaurant Widget
, vou comentar as linhas de 42 a 49 com um ponto e vírgula. Em seguida, vou chamar o método context.go
para o app-router.restaurant
e, como parâmetro opcional, vou passar o nosso restaurante para o extra
.
Isso significa que o restaurante já chega automaticamente na minha tela de restaurante? Não! Na verdade, preciso configurar a minha rota para ter a inteligência de notar que está chegando um extra
, e passar esse extra
para a tela que vai abrir.
Antes de irmos para o nosso router
, vou remover na linha 4 a nossa importação que já não é mais necessária. Voltando ao meu arquivo router
, vamos criar nossa rota para o Restaurant Widget
, ou melhor, o Restaurant Screen
. Vou criar um GoRouter
, GoRoute
, melhor dizendo, o path
vai ser restaurant
, lembrando que é aquela string (cadeia de caracteres) que já tínhamos criado. Vou criar builder
, vou dar um control ponto e vírgula, e agora vou usar a função normal com chaves, porque vai ter mais de uma linha nessa lógica, você vai ver.
O que vou fazer? Vou saber que vou receber um restaurant
, que vai ser igual a state
, state v
. Sempre teve state
aqui, além do context
, na hora de chamarmos uma nova tela via rota aqui no GoRoute
. Esse state
vai lidar com tudo que está ao redor da chamada dessa rota. Então, esse extras
está dentro desse state
. Vamos fazer aqui um extra
, extra
. E aqui posso fazer uma conversão. Como sei que a única forma de chamar essa rota é via aquele restaurant widget
e ele sempre vai mandar um extra
para mim, posso saber que isso aqui vai ser um restaurant
. Então, eu faço sRestaurant
.
Com isso em mãos, o que vou fazer? Vou simplesmente fazer um return
, lembrando, como estávamos usando o error
nos outros casos, ele tem um return
implícito aqui. No nosso caso, ele vai ser explícito. Na linha 27, vou fazer um return
de restaurant screen
, e passo o nosso restaurant
. E pronto! Essa é a lógica.
O meu restaurant widget
já sabe qual é o restaurante. Então, ele passa via extra
no GoRouter
e aí, quando chega lá no router
, sempre que uma rota é passada, ela volta aqui para o meu GoRouter
, ele vai analisar e dizer, legal, eu recebo um restaurante, um objeto restaurante, nesse tal de extra
, que é um espaço para passarmos um objeto, e eu chamo a minha restaurant screen
que precisa da informação do restaurante, usando o restaurant
.
Vamos ver se funciona? Vou salvar. Deixa eu dar até um reload aqui para garantir que não tem nada que ficou pelo caminho. E vamos lá, vou clicar embora. E o que precisa acontecer? Quando eu clicar em qualquer um desses restaurantes, ele simplesmente tem que abrir o restaurante para mim. Vamos lá? Olha, abriu o nosso donuts.
Uma coisa que é importante de notar, já notei que não está tendo um botão de voltar aqui. Isso é porque o nosso restaurant screen
está usando a nossa app bar
também. Então, vamos dar uma olhada no que ficou faltando aqui. Vou já entrar na minha restaurant screen
. E olhando o meu getAppBar
, o que acontece aqui? Porque ele não apareceu para nós o nosso ícone de voltar. O iconButton
, ele manda para o checkout
. Ah, interessante, interessante o que está acontecendo aqui.
Qual é o problema que aconteceu? Veja, quando fizemos o trabalho na nossa app bar
, nós só configuramos o checkout
. E se eu clicar para ir no checkout
, ele vai. Mas tem uma questão. Lembra que o Go
não está mais empilhando. Então, quando tinha um botão de voltar no nosso projeto, antes de começarmos essa refatoração, é justamente porque o nosso restaurant widget
chamava um restaurant screen
usando o push
. Então, a app bar
já tem aquela inteligência de criar um botão para voltar. Vamos ter que fazer a mesma coisa que aconteceu lá com o checkout
com a nossa app bar
. Só que aí tem uma questão. Se eu estou no restaurante, eu quero voltar para a tela inicial.
Se eu estiver em um prato, que veremos mais adiante, não vou querer voltar para a tela inicial. Vou querer voltar para a tela do restaurante. O que isso significa para nós? Que a nossa função getAppBar
precisa identificar qual é, ou melhor, não será ela que vai definir qual é o comportamento desse retorno, mas sim quem chama a nossa getAppBar
. Fazemos isso usando uma função aqui nos nossos parâmetros. Então, além dos que estou passando, vou solicitar aqui, pode ser até antes do title
, já que o title
é opcional, certo? Vou solicitar um required
. Na verdade, nem precisa ser required
, acho. Mas vou fazer sendo required
, porque aí ele vai dar erro onde está sendo chamado o getAppBar
e fica mais fácil até para nós corrigirmos. Vou chamar uma função que vai se chamar onBackPressed
. Pronto, vou salvar, e agora basta dar esse comportamento dentro da minha appbar
. Para isso, antes do title
, na linha 15, vou fazer um leading
, iconButton
. Esse iconButton
, basicamente onOnPressed
, que vai chamar o meu onBackPressed
. Lembre-se que esse comportamento quem vai dar é quem chama a appbar
, e não a própria appbar
. E o ícone vai ser iconsArrowBack
. Agora, você vai ver, vou dar um Ctrl-B
para abrir o explorador, que em alguns lugares deu erro. Já tinha até dois aparecendo aqui, que são lugares que, como posso dizer? Que estavam usando a nossa appbar
, ou melhor, nosso getAppBar
. E agora já estou percebendo um problema aqui na minha lógica, mas você vai ver quando chegar a hora.
Vamos começar com o nosso restaurant
, que foi onde nós notamos o problema. O nosso restaurant
quer voltar para onde? Quer voltar para a home screen
. Então, na minha appbar
, vou fazer, na linha 14 do restaurant screen
, vou fazer um onBackPressed
, passo uma função, e dentro dessa função vai ser um context.goalapprouter.home
. Já está bem claro, né, o que esse goal
faz. Então, approuter. home
. Vou salvar. No nosso dish
, vou querer voltar para um restaurante. Mas só para ele não dar erro, como nós não temos essa lógica ainda de voltar para o restaurante, vou só fazer um onBackPressed
e vou passar um, na linha 31 aqui do meu dish screen
, vou passar uma função vazia. Nada vai acontecer. Só que, na minha home screen
, aí é que está a pegadinha que eu só notei depois. Na minha home screen
, eu não quero voltar para lugar nenhum. É para ter um drawer
, não é para ter um botão de voltar. Então, nesse caso, eu não posso passar nada aqui para a minha getappbar
, entende? Então, o que vou fazer é dar uma refatorada na appbar
que acabamos de criar.
Voltando para o meu arquivo de appbar
, deixa eu dar um Ctrl B
para fechar o explorador. Você vai ter que, no final das contas, que essa função é opcional. Nem todas as telas vão querer ter um botão de voltar pela appbar
. Então, vou colocar um ponto de interrogação depois da função na linha 10 e tirar o required
. E o que vamos fazer aqui? O leading
só vai mostrar o botão de voltar caso essa função seja diferente de nula. Então, na linha 16, leading
, vou fazer onBackPressed
, vou fazer diferente de nulo. Se for diferente de nulo, mostra esse icon bulletin
, tudo direitinho e tal. Se for nulo, na linha 19, deixa nulo. Porque lembre-se, o leading
não é um parâmetro obrigatório da appbar
. Ele pode ser nulo. Então, se eu não passar nada, ele continua sendo nulo. Vamos ver se funcionou.
Vindo na nossa aplicação, se eu clicar para voltar, vou clicar aqui no meu transmissor mesmo, se eu clicar para voltar, ele volta para a minha tela de home
. E, na minha tela de home
, meu drawer
está funcionando direitinho, ele não colocou um botão que não faz sentido aqui. Então, show. Perfeito. Funcionou para nós. Próximo passo. O próximo passo é justamente quando eu entrar em um pedido qualquer, ou melhor, um prato qualquer, ele me mandar para a tela de dish screen
. Não vou clicar aqui porque ele está usando Navigate
e nós já sabemos que ele daria um erro para nós. Então, para fazer isso, vou fazer alguns trabalhos aqui. Vamos notar o que nós vamos precisar fazer. No nosso restaurant screen
, deixa eu já fechar algumas coisas que eu não vou usar.
A main
é sempre bom ter aberta, assim como a route
. Vamos começar nosso caminho pelo restaurant screen
. Vou abrir o arquivo e fechar todos os outros à direita. Perfeito. Meu restaurant screen
vai ter um dish widget
. O que o dish widget
está recebendo? Está recebendo um dish
, que é o prato, e está recebendo um restaurant name
, que serve para mostrar que aquele prato pertence àquele restaurante específico.
Se eu entrar no meu dish widget
, você verá que lá embaixo vai ter um onDishPressed
, que passa o que para a dish screen
da linha 74? Passa o dish
, que é um objeto do modelo da classe dish
, e passa também o restaurant name
. Ótimo. É isso que nós vamos ter que resolver aqui.
Então, a substituição que vamos fazer, já sabemos que é da linha 7077. Já posso adicionar um ponto e vírgula. Mas, se eu fizer simplesmente aqui, context.goal.apphalter.dish
, e eu vou passar um extra
. Só que esse extra
é um objeto só. E agora precisamos passar dois objetos. Como é que podemos resolver essa situação? Teria várias formas de fazer. Uma delas, provavelmente a mais elegante, a mais sofisticada, seria criar uma classe só para isso. Uma classe que contivesse o nome e um dish
para passar. Mas como é só uma informação tão pequena, nós podemos fazer um map
. Então, um map
onde a chave é o meu dish
. E o valor é o restaurant name
.
E aí, eu vou ter que saber, lá no meu router
, quando eu for criar a rota de dish
, que o extra
está dividido dessa forma. Ele é um map
, que a chave é um dish
, e que o valor é um restaurant name
, que é um string (cadeia de caracteres), que é um restaurant name
. Perfeito! No meu dish widget
, eu vou subir e vou remover a minha linha 4, porque não estou fazendo mais uso dessa importação.
Voltando no meu arquivo de router
, vamos criar a minha rota. Então, eu vou fazer na linha 30, puxar um pouco para cima para ficar melhor de ver. Go out
. Passo o path
como sendo o dish
. Chamo um builder
. O meu builder
vai ser o com a função arrow (seta). Estou fazendo aqui mais rápido, usando o contraspaço. Quando surge alguma coisa aqui, é porque eu usei o contraspaço.
E o que é que eu vou fazer? Eu preciso pegar um map
do tipo dish string
, chamado map
, ele pode chamar map
, state.extra
como app map
, melhor dizendo, dish string
. Está dando um erro aqui, porque eu não importei o dish
ainda. Então, eu vou fazer ctrl
. E vou passar models.dish.dat
. E agora, basta eu chamar a minha dish screen
, que esperava um dish
e um string sendo um restaurant name
.
Então, eu vou chamar o return
, return dish screen
. Ela pede meu dish
, que já está sendo passado. Ou melhor, o meu dish
, na verdade, está em map
. E aí, como é que acessamos? Esse map
, nós sabemos que vai ter apenas um elemento. Sempre. Porque só chamamos essa rota via o nosso dish widget
e nós configuramos o dish widget
. Então, ele vai ter sempre um elemento. Então, como é que podemos fazer aqui? Quando eu tenho essa situação, eu posso fazer simplesmente map.keys
. O meu keys
, em qualquer map
, me retorna uma lista de chaves. Mas temos uma chave só, que é o meu prato. Então, eu vou fazer keys.first
. A primeira chave, a primeira e única chave desse map
. Já o restaurant name
, a lógica é a mesma. Como eu tenho só um valor, eu vou fazer map.values
, ou seja, valores. first
. Ponto, uma vírgula, ponto e vírgula. E pronto.
Agora sim, eu tive uma forma de mandar a informação e recebê-la aqui no GoHalter
. Vamos salvar. Deixa eu abrir o meu transmissor. Beber uma água. Vou fazer o seguinte. Vou até reiniciar a aplicação. Por quê? Nesse ponto, teoricamente, terminamos a nossa refatoração para o GoHalter
. Tiramos tudo que era de Navigator
e colocamos a lógica do GoHalter
. Beba sua água aí também.
E eu vou simplesmente clicar aqui em ir embora. Perfeito. Entra aí. Entra aí. Do splash
para a home
funcionou. Então, eu estou a fim de comer gyoza. Vou pesquisar aqui. Gyoza. Só gyoza já vai. Ah, tem gyoza no Panda Sushi. Ótimo. Vou entrar. Vamos procurar meu gyoza. Eu sei que ele está lá embaixo. Então, da home
para a Restaurant Screen
funcionou.
Vamos entrar no nosso prato. Funcionou. Então, a tela do restaurante conseguiu enviar para a tela do prato passando o nome do restaurante. Você pode ver aqui em cima. Vou até clicar para você ver. E a informação do prato. Vou adicionar ao meu carrinho. Quero oito gyozas. Esse gyoza é muito caro. Ah, deve já vir com oito gyozas. Então, vou adicionar só um. Entrei muito no roleplay. Coisa de quem joga RPG. Beleza. Vou clicar no meu Checkout. Vamos ver se estou conseguindo chegar no Checkout. Legal. Cheguei. O meu gyoza está aqui. Ó, quero mais. Vou adicionar aqui mais um. Meu total muda e tal. Vou voltar. Vamos ver se do Checkout para a Home está funcionando. Show. Voltei. Ótimo. Mas tem algo que nós precisamos verificar aqui. Ah, a saída. Se eu clicar no meu Draw e ele clicar para sair. Voltei. Então, o que aconteceu? Conseguimos fazer a refatoração completa do Navigator para o Go-Router. Mas lembre-se que o nosso objetivo é o Deep Link. Nós queremos entrar num restaurante específico, num prato específico, através de um link. Só que tem um detalhe que dá para fazer usando o Go-Router. Agora nós vamos para o além. Vamos adicionar funcionalidades usando o nosso maravilhoso Go-Router. Uma coisa bem importante que você como pessoa desenvolvedora vai usar muito com o Go-Router são animações específicas para transições entre telas específicas. Então, por exemplo, vou clicar aqui para entrar e o meu Checkout. O meu Checkout poderia ter uma animação diferente. Como se desse a ideia de que ele está sempre ali disponível. Afinal, eu posso acessá-lo de praticamente qualquer tela. Então, eu queria uma animação diferente para o meu Checkout. Uma animação de slide. Como se fosse aqui da direita para a esquerda da aplicação quando eu clicasse. Mas, por enquanto, ele está usando a animação normal. Vamos corrigir isso no meu Go-Router? Para fazer isso, na minha tela de Checkout. Ou melhor, na minha rota de Checkout, na linha 25. Eu não vou mais usar um Builder. Não vou querer mais usar esse Builder. Eu posso até tirar. Tem outra forma de construir a tela aqui mesmo na nossa rota. Então, do Builder até o final do Checkout Screen. Vou deletar. Ainda na linha 25, claro. E vou fazer um contraspaço e chamar por PageBuilder. PageBuilder. Criar uma página. E mais uma vez, o PageBuilder. Como é um Builder, eu posso usar aquele macete de já selecionar para mim qual é o callback que ele espera. Que é um ContextState igual ao Builder normal. O que é que vamos retornar aqui? Vou fazer um Return. E não vou retornar o Checkout direto. Eu vou retornar uma Custom Transition Page. Ele já até sugeriu aqui para mim. Essa Custom Transition Page serve para, dentre outras coisas, configurar uma animação de transição para essa página específica. Qual é o Child? O Child é justamente a nossa tela de Checkout. Então, agora sim, eu posso chamar aqui Checkout Screen. Há algumas configurações que precisamos fazer, além do TransitionsBuilder, que é a mais importante para a animação, então vou deixá-la por último. Mas uma coisa bem importante de definir também é uma Key. Por que definir uma Key é importante? Key aqui no Flutter a gente sabe que serve para nós criarmos, quando estamos mostrando telas, o widget é diferente, o Flutter entender, caso seja a mesma tela, a mesma ideia, o mesmo objeto, ele não precisar reconstruir tudo, ou sequer precisar reconstruir, só mostrar, ou se for algo diferente, de fato, usando a mesma instância de uma tela, por exemplo, aí sim ele reconstruir se for necessário. Então, o Google Router indica que nós passarmos aqui em Key um State. pageKey. Dessa forma, isso é uma recomendação do Google Router, toda a noção de transição entre rotas vai funcionar melhor, quando usarmos o Constant Transition Page. Próximo passo, que não é necessariamente obrigatório, mas eu já quero definir aqui, é a duração dessa transição, ou seja, a duração da animação.
Vou definir um Transition Duration
na linha 31, passando um Duration
. Esse Duration
será, vamos lá, em milissegundos, uns 500 mil. Vou deixar um valor alto por enquanto, só para nós vermos, e depois diminuímos. Vou definir, sei lá, 2 mil, 2 segundos, 2 mil milissegundos, o que equivale a 2 segundos.
Vamos para o nosso Transition Builder
, linha 32, show! É um Builder
? Legal, já sabe o truque, corrige a parte que está dando erro, dá um espaço, e vê como é importante. Muita coisa nesse callback (retorno de chamada), né? Vou selecionar o segundo, que eu sei que é sempre o das chaves, e vamos trabalhar nele aqui.
Qual será o Return
? O Return
será um Slide Transition
. Aqui você poderia usar qualquer Transition
que termina, qualquer widget que termina com Transition
aqui do Flutter, tá? Eu vou usar o Slide Transition
, porque eu quero realmente fazer um Slide na tela.
Qual será a Position
? Agora nós precisamos relembrar nossos conceitos de animação. Então eu vou fazer um Animation
, esse Animation
está sendo controlado pelo meu Custom Transition Page
, então já recebo ele aqui do próprio Builder
, só que aí eu vou fazer um .drive
para direcionar essa minha animação.
O que esse drive
espera? Ele espera um Tween
. Tween
significa entre um estado e outro. Então aqui no meu drive
eu vou passar um Tween
, um Tween
, sempre esqueço como se escreve Tween
, mas é assim, Tween
. Esse Tween
será do tipo Offset
. Offset
é uma posição na tela. Porque estamos usando o Slide Transition
, você vai ver que essa posição é uma variação, uma animação de Offset
. O Offset
é basicamente uma coordenada, x e y. Então deixa eu colocar um ponto e vírgula no final só para ficar organizado.
E dentro do meu Tween Offset
ele vai ter um Begin
e um End
, onde vai começar e onde vai terminar. O Begin
será um Offset
, e aí ele me dá dx e dy. Como eu quero variar só no eixo x e não no y, eu vou justamente só variar no x e não no y. Então no meu Begin
o x será 1 e o y será 0. No meu End
o Offset
terá o x 0 e o y 0. Vou colocar vírgulas aqui para deixar tudo certinho.
E só para finalizar, o meu Slide Transition
precisa saber o que ele vai mostrar no final. E isso nós definimos através do Child
. Esse Child
eu já recebo do meu Transition Builder
, que eu já estava recebendo do Custom Transition Page
. Então eu vou passar o meu Child
. Vou salvar.
Isso tudo é conceito de animação, mas usamos aqui o Custom Transition Page
para definir uma rota, que sempre que eu chamar essa rota, vai ocorrer essa animação para mostrar de novo na tela. Vamos ver se vai mesmo? Então vou reiniciar a aplicação. Abrir o meu transmissor. Nem estou chamando tanto de emulador. Então vou clicar embora. Cliquei embora. E vamos lá, vou clicar no meu Checkout
. Vamos ver. Nossa, bem lento, mas deu para ver que está funcionando. É claro, não faz nenhum sentido deixar dois segundos.
Na linha 32 eu vou reduzir para 500 milissegundos. Meio segundo. Vou salvar. Vou voltar. E agora quando eu clicar... É, eu esqueci de dar o Reload
. Então ele não carregou minhas alterações, porque lembrando, esse Route
é uma classe abstrata, que vem via Esthetics
, chamado lá na main
. Então quando eu tenho alteração aqui, eu preciso dar o Reload
para ele subir de novo essa alteração. O Route Reload
não funciona para nós. Então vou fazer aqui embora. Vou clicar agora no meu Checkout
. E olha, foi bem rápido. Acho que dá até para ser mais rápido. Vou reduzir perto das ventas. Salvar. Iniciar essa aplicação. E vamos ver se fica mais rápido.
E uma coisa bem importante. Sabe o que seria legal, pensando aqui? Seria legal que... Quando eu fosse para a sacola, ele abrisse para a direita. E quando eu voltasse da sacola, ele voltasse tirando ela da frente. Seria legal, né? Então a transição de voltar para a Home
, fosse da Home
, né? Assim, puxando para o outro lado. E para fazer isso, sem influenciar nas outras rotas que chamam a Home
, por exemplo, Restaurante
, eu teria que criar uma rota diferente, que só a Checkout
chamaria. Isso é um bom desafio, um bom exercício prático. Um bom exercício prático, quase que não sai. Para eu deixar para vocês logo na sequência.
Então, não deixe de fazer. Mas é basicamente usar os mesmos conceitos que usamos agora na animação. Legal. No entanto, temos um ponto importante que não podemos esquecer. Nosso objetivo aqui são os Deep Links. Queremos simplesmente entrar em um "Taco Supremo", por exemplo. Tenho um link, que com esse link, eu caia direto aqui no meu "Taco Supremo". Sem passar por nada antes. E isso usando até o navegador da pessoa usuária.
Mas para fazer isso, será que nosso programa já está, nossa aplicação já está pronta para isso? Só de ter feito a refatoração por GoHowter, já garante que isso está pronto? Na verdade, não. E aí entramos em um conceito chamado autossuficiência das telas, ou independência das telas, digamos assim.
Mas para mostrar isso na prática, quero te mostrar uma coisa. Vou parar a aplicação e vou mudar. Ao invés do meu dispositivo físico, que está conectado via wireless aqui no meu computador, vou escolher o Chrome. Então não vamos usar mais o Flutter Android, vamos usar o Flutter Web. Sim, o Flutter Web é útil várias vezes. Gosto muito do Flutter Web. Entendo que existem até outros frameworks que são específicos para a web, mas o Flutter Web também é poderoso e ele às vezes pode servir até para nós que estamos desenvolvendo para Android, para iOS, para testar esse tipo de coisa.
Por quê? Porque quando eu abrir no meu Chrome via Flutter Web, vou ter acesso à barra de URL. Então vou conseguir visualizar como minha aplicação está se comportando em cada URL que eu entro. E vamos ver que tem uma coisa bem grave acontecendo. Mas que nós, é claro, vamos conseguir corrigir. Deixa só ele abrir para mim. Ele abriu no meu monitor de lado aqui, então vou puxar para cá. Vamos esperar ele abrir. E olha, ele abriu horroroso, é claro, porque essa aplicação não foi pensada para web, muito menos web em computadores. Antes fosse web no próprio navegador de celular.
Para não ficar tão feio, vou dar um F12. O F12 abre o DevTools do Google Chrome e aí ele já mostra para mim. Se não estiver bonitinho assim para você, você pode clicar nesse símbolo, está bem pequenininho, mas ele fica perto da palavra elements, que varia entre uma visualização de computador e uma visualização de celular. E aí fica legal. Se der um bug na visualização, que pode acontecer, não aconteceu comigo, basta mudar aqui o Dimensions para algum smartphone que você queira. Pode ser o iPhone SE, o iPhone XR, que o bug some, pode acontecer.
Beleza, mas você já consegue notar aqui, desconsidere o localhost, porque é claro, estou rodando na minha máquina. Poderia ser um techtext.com.br
e por aí vai. Mas, da hashtag para frente, o que temos? Barresplash. Porque configuramos nossa rota inicial para ser um barresplash. Se clicarmos em Embora, o que vai? Vai para Home. Se clicarmos em Checkout, ele vai para Checkout.
Mas e o que acontece se eu clicar em uma aplicação, ou melhor, uma tela, que precisa passar uma informação? Olha, vou clicar em Monstro Burger. Eu caio só em Restaurant. Porque foi assim que nós definimos. Eu não tenho informação de qual é o restaurante. Não é Restaurant barra Monstro Burger aqui, sabe? Que faria sentido. Não é, eu tenho só Restaurant. E por que isso está acontecendo? Porque a informação de qual é o restaurante está sendo passada por debaixo dos panos, usando aquele extra.
Ele é muito útil, se fosse o caso de só usar o GoHowter, como nossa forma de navegação entre telas, que tem várias outras vantagens além do DeepLink. Seria ótimo, mas, para o nosso caso, nossa tela deixa de ser independente. Ela deixa de ser autossuficiente. Porque se eu caio na minha tela de Restaurante, ele não me diz qual é o restaurante que eu estou usando, que eu estou acessando. Pior ainda se eu caio na minha tela de Prato, que só tem escrito Deixe Ali. Não quer dizer nem qual é o restaurante e nem qual é o prato que eu estou mostrando. Isso é muito ruim. Não dá para usar DeepLink dessa forma.
Esperaríamos algo como Restaurant
, e então teríamos o ID do Restaurant
, qualquer coisa aqui. Em seguida, Deixe
, e finalmente, o ID do Deixe
. Nossa rota teria a inteligência de enviar para a nossa tela qual é o restaurante, e a tela, uma vez autossuficiente, conseguiria carregar todas as informações e exibi-las, independentemente de quem a acionou. Porque, lembre-se, a pessoa pode simplesmente ter esse link. Ela pode não ter passado pela Splash
, não ter passado pela Home
, e então não teria os extras para enviar. E apenas com esse link, ela teria que cair no meu Restaurant
ou no meu Deixe
. Posso até testar no Restaurant
aqui. O que acontece? O que ocorreu? Tentei entrar diretamente no Restaurant
. Dá um erro de nulo, porque não tem um Restaurant
sendo passado no Extra
, já que eu, como pessoa usuária, tentei entrar direto no link. Vamos resolver isso?
Para resolver isso, vamos começar no nosso Router
. Vou fechar o meu Google Chrome, se eu precisar, eu abro ele de novo. Mas, vindo no nosso Router
, vamos mudar como a nossa rota de Restaurant
funciona. O nosso Path
, ele não vai ser apenas mais Restaurant
. Ele vai ser Restaurant
mais uma string chamada RestaurantId
. Esse RestaurantId
é arbitrário. Eu poderia colocar qualquer coisa aqui. Estou colocando RestaurantId
, porque é o padrão que se usa. Mas, o que importa são esses dois pontos. Quando eu tenho dois pontos na minha rota, tudo que vier depois e que não seja outra barra ou outros dois pontos, ele vai detectar como se fosse uma variável. Então, vou conseguir pegar, ou melhor, como uma chave de um Map (Mapa), que no futuro vai virar uma variável. Então, vou conseguir pegar esse RestaurantId
, o que tiver nele. Então, se a pessoa seguir esse padrão, ela escrever Restaurant
e passar um Id aqui, vou conseguir pegar via esse RestaurantId
. Ele está dando esse aviso apenas para eu substituir por interpolação. Fica dessa forma mais elegante. Só que, ao invés de usar Extra
, agora, o que eu vou fazer? Vou tirar essa linha e vou passar String RestaurantId
igual a State
. E então, vou procurar aqui por PathParameters
, melhor dizendo, é uma palavra chata de falar, mais PathParameters
, que vai ser um Map (Mapa).
O método map
espera todos os parâmetros que nós definimos na rota usando dois pontos. Então, vou passar aqui dois pontos, restaurant
, restaurant_id
. Lembrando, tem que ser exatamente igual. Posso até fazer um ctrl+c
e um ctrl+v
. Porém, um detalhe que quase me passou despercebido. Não preciso dos dois pontos. Os dois pontos são usados apenas na rota para identificar essa chave. Quando estou chamando aqui no meu map
, não preciso dos dois pontos. E por que está dando erro? Porque esse id
pode simplesmente não existir. Então, essa string (cadeia de caracteres) pode ser nula. E aí sim, ele deixa de dar erro se eu coloco a string como sendo nula.
O que vou fazer? Se a string for diferente de nula, restaurant_id
for diferente de nulo, posso chamar minha tela de restaurante. Se não for, se for nulo, ou seja, como é um return
, nem preciso do else
. Posso colocar simplesmente outro return
aqui. Vou chamar a minha not_found_screen
e agora ela se mostra. Por que isso é importante? Porque agora a pessoa usuária vai poder digitar qualquer coisa no nosso caminho. Esse caminho pode não existir. Então, é importante ter uma tela de página não encontrada.
Porém, tem um detalhe. A minha restaurant_screen
, ela não está preparada para receber um id
ainda. Ela está preparada para receber um objeto restaurante. Então, precisamos fazer uma refatoração para que ela receba apenas um id
via string (cadeia de caracteres). E seja capaz de ter a inteligência de buscar na nossa base de dados qual é o objeto restaurante que essa id
representa. Vamos fazer isso?
Para fazer isso, vou na minha restaurant_screen
e vou substituir na linha 10 de restaurant
por string. E de restaurant
, é só restaurant
, por restaurant_id
. Vou chamar também aqui no meu required
, no construtor da linha 11, this.restaurant_id
. Porém, aí começa a dar um monte de erro, certo? Claro, porque a tela ainda não está preparada para lidar.
A primeira coisa que preciso fazer é converter isso de um stateless widget
para um stateful widget
. Claro, nós vamos ter a nossa forma aqui de fazer essa detecção, de entender que chegou um id
e buscar na nossa base de dados qual é o objeto que esse id
representa. Mas isso varia de projeto para projeto. Qual é a arquitetura que você está usando, o banco de dados que você está usando, se você está pegando isso da nuvem, por aí vai. Nós vamos fazer aqui para a realidade do text-taste
.
Então, no nosso stateless widget
na linha 9, vou clicar na lâmpada que é a state
de refatoração e vou clicar em stateful widget
. Agora sim, como essa tela pode mudar, preciso transformá-la num stateful widget
. E preciso também do init state
do stateful widget
para fazer toda essa lógica.
Dentro da minha classe de estado, o que vou fazer? Vou fazer um bool
, interrogação is_not_found
. Porque o que pode acontecer? A pessoa usuária pode colocar o id
de um restaurante que não está cadastrado. Então, também preciso lidar com essa situação. E outra coisa que vou precisar aqui é do próprio restaurante, o objeto restaurant
, restaurant
, interrogação restaurant
.
Nossa missão é limitar esse restaurant
. Vamos fazer isso no nosso init state
. Linha 23, posso tirar o nosso todo
. E depois do init state
, vou chamar um método do Flutter, que é muito importante que você conheça, porque ele é muito útil conforme seus códigos vão ficando mais avançados. Por quê? Quero chamar um set state
para mudar esse restaurante quando descobrir qual é o restaurante que tem esse id
.
Preciso também pegar as informações que estão vindo do id
, então preciso fazer todo esse trabalho antes de mostrar alguma coisa para a pessoa usuária. Porém, essas coisas precisam que a tela já esteja carregada. E aí, se eu só colocasse aqui no init state
, ele ia fazer isso antes de carregar a tela, e isso causaria um erro para nós.
Existe até um pacote bem famoso que você pode ter ouvido falar, que é o afterLayout
, que ele adiciona um mixin
na nossa classe de estado, e aí eu teria outro método chamado... Quer dizer, do tipo override
, que eu sobrescrevo, que faz a mesma coisa. Ele roda um bloco de código depois que o primeiro frame (quadro) é renderizado.
Vamos fazer aqui sem usar esse pacote, utilizando o código nativo do Flutter. Para isso, vou utilizar o widget.binding.instance.addPostFrameCallback
. Dentro desse callback (retorno de chamada), consigo inserir o código que desejo que seja executado assim que o primeiro frame for renderizado, evitando qualquer tipo de problema.
O que vou fazer? Quero buscar o nosso restaurantData
, que é um arquivo que guarda a nossa lista de restaurantes. Se voltarmos à nossa main
, veremos que ele é carregado antes mesmo da aplicação iniciar. Na linha 3 temos um restaurantData
, carregado usando getRestaurants
. Isso vem de um JSON que está nos meus assets, então pode demorar um pouco.
Como não queria fazer esse carregamento sempre que mudasse de tela, fiz esse carregamento na main
e o passei globalmente, com a fonte única da verdade, usando o nosso provider. Para pegá-lo no meu restaurantScreen
, posso vir em restaurantScreen
e fazer context.read
. Não preciso chamar o provider e fazer com que ele fique ouvindo, porque só quero consultar uma informação, não quero que ele fique ouvindo isso o tempo todo.
Com o read
, consigo pegar o restaurantData
, e nele consigo a minha lista de restaurantes. O que vou querer fazer é verificar nessa lista qual é o restaurante. Para isso, estou usando o método where
, método de lista, sem segredos aqui. id
é igual a widget.restaurantId
, fechou? Vou dar um ponto e vírgula.
Isso nos retorna um iterable do tipo restaurant
. Vou chamar aqui de carry
, que é a minha consulta. Só que esse iterable, que é um tipo primitivo de lista, pode estar vazio. Claro, se eu passar um id
que não existe, ele não vai achar nada na minha base de dados. Então vou fazer, se carry.isEmpty
, porque ele não está pegando aqui, ah, é porque eu saí do meu callback. Pronto. Usei alt
e seta para cima para puxar para a linha 30. Agora sim eu posso pegar o carry
.
Se for vazio, o que é que eu vou fazer? Vou fazer que isNotFound
seja igual a true
. Não achei. Se não for vazio, faço que restaurant
, que estava como nulo, passe a ser carry.first
, porque vai ser só um que vai ter o id
, já que o id
é único. Então eu pego o primeiro elemento. E por fim eu posso fazer um setState
para atualizar a tela.
Agora basta atualizarmos nosso build
para ter a lógica de notar as situações que a tela não vai ter sido achada, que ainda está carregando e quando achar de fato um restaurante. Primeiro, caso não ache um restaurante, vou fazer aqui um dentro da build
. Não se preocupe, a build
é só o método do stateful e do stateless widget que constrói a tela. Ele é um método, então ele espera um retorno só.
Se eu passar outros retornos a depender da lógica, ele vai funcionar contanto que eu garanta que algum retorno está sendo passado. Então vou fazer um teste aqui. Se isNotFound
for diferente de nulo e isNotFound
for true
, eu posso fazer um return notFoundScreen
. Ele mostra para mim a nossa tela já pré-pronta de tela não encontrada.
Agora, se restaurant
for diferente de nulo, só se o restaurant
for diferente de nulo é que eu posso mostrar minha tela de restaurante. Faz sentido, né? Se o restaurant
é nulo, eu não tenho o que mostrar. Então vou cercar todo o bloco de código que já temos com esse meu if
. Para fazer isso, vou recortar o fecha chaves na linha 49 e soltar lá embaixo depois do meu scaffold
.
Agora eu garanto que esse restaurant
não é nulo. Então toda a minha tela que estava recebendo um restaurant
anteriormente e não está mais, eu posso fazer um !
em todas elas para dizer, olha, eu garanto que não é nulo. Linha 51! Exclamação, né?
Eureka! Na linha 66 também! Exclamação, após o restaurant
. Na linha 76 também! Na linha 79 também! E por fim, na linha 82 também! Só falta uma situação, que é quando o restaurante ainda é nulo e não foi encontrado, ainda está pesquisando na nossa... Apesar de ser bem rápido, ele demora um pouco para pesquisar. Nesses casos, aqui já no final, eu vou fazer um return
. Return
. Return
. Ou melhor, tem que ser dentro da penúltima chave, certo? Return
. E eu vou passar um scaffold
. Scaffold
tendo como filho apenas um center
com o center
tendo um circular progress indicator
.
Então qual é a lógica aqui? Se ele ainda estiver procurando, que vai ser bem rápido, mas se ainda estiver, ele vai mostrar um circular progress indicator
. Se ele procurou e não achou, ele vai cair no is not found
e vai mostrar a nossa tela de not found
. E caso ele tenha achado, ele alimenta o nosso restaurante e aí sim, ele vai poder mostrar a tela que já era mostrada antes. Entendido?
No nosso router
, ele está dando um erro porque não é mais restaurant
, e sim restaurant id
. Vou salvar. Vamos dar uma olhada se tem algum erro acontecendo. Foi uma refatoração bem grande, então é bom dar uma olhada. Nenhum erro. Entendido? Vou puxar um pouco para cima. Vou tirar aqui a linha 2 que nós não estamos mais usando o módulo de restaurant
, já que é o próprio restaurante que está tendo a inteligência, está tendo a autossuficiência de fazer esse carregamento. E eu vou rodar como Chrome.
Vou rodar como Chrome porque eu quero ter a URL. E se funcionar toda a transição de telas no Chrome, ele vai funcionar também no Android. Mas eu quero rodar no Chrome só para conseguir ver como é que a URL vai se comportar. E eu já estou pensando num detalhe que nós deixamos passar aqui, que é lá no nosso restaurant widget
. Enquanto ele carrega, eu já vou fazer isso. No nosso restaurant widget
, abri o explorador, pasta restaurant
, ou melhor, na pasta home
. Com licença, Chrome. Na pasta home
, eu vou clicar em widget
, restaurant widget
. Lembrem que é o nosso restaurant widget
que chama a nossa tela de restaurant
. Só que ele está chamando ainda com extra
. E não é mais extra
que nós queremos. Nós queremos dar essa autossuficiência para a tela de restaurant
.
Então, vou passar, não mais um extra
, mas seguindo aquele padrão que nós estávamos usando lá no nosso router
. Então, vou concatenar para nós um restaurant
, restaurant
, restaurant id
. Não tenho aqui um restaurant id
. Vamos ver. Ah, é porque eu coloquei hashtag cifrão. Agora sim, restaurant.id
. Ele está dando essa questão de interpolação. Posso dar um contraponto e pedir para ele mudar para interpolação. Vou colocar uma barra, porque, olha, é o caminho, o domínio do restaurante, barra restaurant id
. Vou salvar. E agora vamos ver se funciona de fato. Vou vir no meu Chrome. Vou dar um F12 só para ficar bonitinho. Vou clicar em Embora. Olha, o bug que eu tinha falado. Nesse caso, só seleciona outro e pronto. Ele desbuga. Vou clicar em Embora. Vamos ver. Chegou na tela de Home, ótimo. Sempre atenção na nossa URL. E se eu clicar em Monster Burger, olha que legal. Algumas coisas aconteceram aqui. Primeiro, ele mudou, tudo bem, para restaurant
e passou o id
, mas ele deu página não encontrada. Pode ter sido algum problema lá na lógica da nossa página.
Então, vamos dar uma olhada, se nós deixamos passar alguma coisa abatida. Cheguei aqui. Então, vou dar uma olhada. Vamos ver. Ele procura por um restaurante. Se o restaurante for vazio, not found
é true
, que é a situação em que ele cai em restaurant
. E se não for vazio, ele coloca no restaurant
, entendido. Ele vai fazer um set state
para mudar o estado da tela, legal. E a única forma de ele cair aqui no not found
é mostrando via not found
. Então, estou suspeitando de alguma coisa. A lógica aqui, ela não parece estar incorreta. Eu estou realmente achando um pouco estranho. Eu vou só dar um reload
e vamos tentar de novo. E uma coisa interessante, quando eu dei o reload
, ele não voltou para a tela inicial, porque eu já tenho um caminho pré-definido aqui no meu Chrome. Então, ele passa restaurant
e passa o caminho.
Se eu abrir essa outra aba, agora estou completamente independente. Vou abrir outra aba e clicar. Ele vai tentar acessar a minha página do meu restaurante "Monster Burger". No entanto, está ocorrendo algum problema, pois ele está sendo direcionado para a página "não encontrada". Vamos verificar se o problema está no router.
Chegando aqui no router, temos o nosso caminho, restaurant/barra restaurant id
, e eu tento pegar um restaurant id
. Se o restaurant id
for diferente de nulo, eu passo esse id. O que será que está causando problema aqui? Vamos dar uma olhada.
Ele não tem os dois pontos, ok? Os dois pontos só são usados na hora de passar o path. A barra está correta aqui. O nosso caminho também está bem definido. Então, vamos fazer o seguinte. Por que não? Por que não fazer isso ao vivo? Vamos depurar.
Vou clicar aqui na linha 52 e vou colocar um breakpoint. Vou fechar essa aba que eu abri e reiniciar a aba principal. Então, vou dar um refresh. Ele vai forçar a cair naquela rota. Legal. Caiu na minha rota e vamos ver o que está chegando. Ele recebeu o id. Ok, o id ele consegue passar. Então, vou remover esse breakpoint. Vou entrar no meu restaurant screen
. E vou entrar na lógica que nós fizemos de pesquisa.
Então, vou seguir com o continue. E ele cai aqui para mim. E vamos ver como é que essa Carry está se comportando. A Carry tem cinco itens. Cinco restaurantes que têm esse id. Então, vamos dar uma olhada. Teoricamente, cada restaurante deveria ter um id só, certo? Então, restaurante aqui. Ele está com a lista. Ok, legal. Mas se for vazio, ele cai no not true. Mas Carry is empty. E ele está caindo em true. Mesmo nós vendo que o Carry, ele não está empty.
Vamos fazer o seguinte. Vou parar e vamos lidar com uma lista ao invés de lidar com um iterable. Talvez esteja sendo mais ou menos por aí esse problema que está acontecendo. Para fazer isso, na linha 28, vou converter para lista. List. Show. E dentro do meu add, ou melhor, depois do meu add na linha 31, vou fazer um toList. Pronto.
E agora, com isso em mãos, eu posso fazer até aqui um carry. Carry. Carry. . length. Length. Igual a zero. Se for igual a zero, e ele já está dizendo aqui, não use igual a zero, use is empty. Ah, vou usar o is empty. Se ele for vazio, ele cai em not found. Se ele não for, ele vai pegar o zero aqui. Na posição zero. Porque agora nós estamos lidando com lista. Vamos ver se agora funciona. Vou rodar de novo. E vamos ver o que acontece. Show. Começou a carregar. Eu nem vou me preocupar muito com a visualização, ok? Eu vou só clicar embora. E vou clicar no Monster Burger. E legal, ele caiu aqui. Vamos ver o que está acontecendo mais uma vez. A nossa Carry... Agora sim, a nossa Carry está vazia. Por que será que a nossa Carry está vazia? Eu estou passando um Restaurant ID
. O que está chegando aqui? Vamos dar uma olhada no this. No... O Restaurant
está vazio. O que será que faltou aqui? O widget, ele recebe um Restaurant ID
. Widget. O widget, vamos ver. Vamos procurar por Restaurant ID
. Olha, Restaurant ID
está recebendo barra Restaurant
. E realmente, não tem nenhum restaurante com ID barra Restaurant
. Então, por que será que está acontecendo? Sendo que a minha rota está correta. A minha rota... Deixa eu até colocar ele para rodar aqui. Remover o Breakpoint. A minha rota está correta. Barra Restaurant
. E aí sim, eu recebo o meu ID. Então, em algum momento, eu estou passando barra Restaurant
em vez de passar o ID. Aqui não deve ser. Então, pode ser na hora que eu estou chamando. Será? Deixa eu colocar mais uma vez o Breakpoint aqui. Vamos ver o que acontece. Deixa eu chamar ele de novo. A minha rota, né? Ah, eu tenho que atualizar a página. Vamos ver. Ele vai cair lá na minha rota. E o Restaurant ID
é, de fato, o Restaurant ID
. Ah, notei o que está acontecendo aqui, pessoal. Nossa, foi um detalhe muito traiçoeiro comigo. Veja, lembra que nós temos o nosso caminho na linha 15. Se você notou isso, você tem uma visão de águia. Mas nosso caminho na linha 15 se chama Restaurant
. Ótimo, legal.
No meu Restaurant Screen
, estou passando Restaurant
e não Restaurant ID
. O ID que estou pegando. Portanto, não havia nenhum problema na nossa lógica. Tudo estava funcionando. Foi apenas um pequeno erro de digitação, pois estávamos convertendo daquele objeto que antes chegava pelos actions. E agora, passando o mouse no debug, vejo que está passando o ID. Então, nenhum problema na nossa lógica. Foi apenas uma questão de que estava passando uma string errada por uma confusão na hora de digitar.
Vamos ver funcionando, de fato, agora. Porque agora eu quero ver isso funcionando. Vou abrir o meu navegador. Ele já me deu spoiler de que funcionou, mas vou fingir que nada aconteceu. Vou apagar o caminho. Entrei no meu site. Entrei aqui. Vou dar um F12 para fingir que estou no celular. E vou clicar em ir embora. Deu um bug. Posso mudar para outro. Não tem problema. Caí na minha página home. Se eu clico em Monster Burger, chego no Monster Burger. E o mais importante, eu quero ver a independência dessa tela. Então, vou dar um CTRL C nesse caminho. Vou dar até um CTRL SHIFT N. Vou entrar em modo de navegação anônima. E vou dar um CTRL V. Ele tem que conseguir carregar a tela do Monster Burger sem passar pelas outras telas. Vamos ver o que acontece. Muita expectativa. Vamos ver. Vamos ver. E está lá. Nosso Monster Burger chegou.
Agora, vamos fazer essa mesma lógica para os pratos. Se eu posso chegar no restaurante, eu tenho que poder chegar também nos pratos. Para fazer isso, agora é só seguir a mesma ideia que tivemos para fazer o do restaurante. Só com uma única diferença. Lembra que eu disse que eu queria que o prato fosse algo como? Eu digo qual é o restaurante e depois eu passo um barra deste. E aí eu passo o ID do prato, porque um prato só faz sentido se ele estiver num restaurante.
Então, para fazer isso no Google Router, ao invés de passarmos uma rota na raiz aqui da nossa lista, nós vamos passar uma rota como subrota do meu restaurant ID
. Ou melhor, do meu restaurant
. Então, na minha rota de restaurant
, ele pode ter N subrotas. Inclusive, a nossa rota para prato. Então, da linha 61 até a linha 70, eu vou só comentar. Porque não vai ter uma rota direta para prato. O prato só faz sentido dentro de um restaurante. E na minha route do meu restaurante, depois de eu definir o builder, então na linha 59, eu vou procurar por routes. Que também recebe um route base e uma lista de rotas. Essa lista de rotas, eu vou passar um Google Route, passando o dish. Só que não é mais dish, nós sabemos que vai ter que ser algo como o caminho de dish barra dish ID. Porque eu quero passar o ID do dish também. E vou chamar o meu builder. Não precisa desse ponto e vírgula.
E o que eu preciso fazer é pegar o restaurant ID
e o dish ID
. De onde vai vir o restaurant ID
? Por isso é importante colocar como subrota. Vai vir da rota pai. Da rota pai, que é a rota restaurant ID
. Então, dentro da subrota, eu tenho acesso aos parâmetros da rota. Então, eu posso fazer simplesmente um string interrogação. Restaurant ID
. Não vou cometer o mesmo erro, autocomplete. Vou até escrever na mão. Restaurant ID
. E eu vou receber um state.path parameters
. Passando restaurant ID
. Alt shift setinha pra baixo. Restaurant ID
vai deixar de ser um restaurant ID
e vai ser dish ID
. Só copiar a linha para ganharmos tempo. No parâmetro recebido, também dish ID
.
E o que eu preciso fazer? A verificação, primeiramente. Se restaurant ID
for diferente de nulo. E dish ID
for diferente de nulo. Eu chamo meu return
, return dish screen
. Screen
. Passando dish ID
. Dish ID
. E não vai ser my restaurant name
, vai ser restaurant ID
. Passando restaurant ID
. Está dando erro, é claro, porque ainda não configuramos a nossa dish screen
para ter essa lógica de pegar o restaurante e o prato do nosso banco. Mas só para terminarmos a nossa rota, caso qualquer um desses seja nulo, não temos condições de mostrar nada na tela. Então, return 71
, linha 71, return not found
. Not found screen
. Vou salvar e vamos trabalhar na nossa dish screen
. Deixa eu fechar tudo a direita. Vou clicar no alt, fechar a direita. Clico com ctrl, dish screen
. E eu sei que agora eu não vou receber mais um dish.
Eu não quero que isso seja entregue de bandeja. Quero receber uma string que é dish ID
. E também não quero mais receber o nome do restaurante. Isso eu consigo acessar no banco. Quero receber um restaurant ID
. Preciso corrigir na linha 17 e 18 os meus parâmetros no construtor. Então, dish ID
e restaurant name
se tornam restaurant ID
. E agora vamos fazer a mesma lógica.
Na minha classe de estado, vou fazer primeiro um restaurant
. Posso fazer um not found
. bool ? is not found
. Vou precisar também de um restaurant
. Restaurant. ? restaurant
. E vou precisar também de um dish
. ? dish
. Esse restaurant
vou definir na linha 28. Contra o ponto para importar o meu model. E depois da definição, vou na linha 34 fazer um init state
. Vou chamar aquele mesmo método. Widget binding. instance. Add
, ou melhor, post. Add post frame callback
. Passo um callback. E dentro desse callback, agora vou fazer a minha lógica.
Ela é bem parecida com a lógica do restaurante. Com a diferença que vamos ter que pegar não só o prato, mas também o restaurante que esse prato possui. Que possui esse prato, melhor dizendo. Então, começamos procurando um restaurante. Afinal, um restaurante é que possui um prato e não um prato que possui um restaurante. Vamos fazer. Um iterable. Vimos que o problema não foi nosso iterable. Iterable restaurant. Carry restaurant
. Vou chamar carry rest
. Igual a context. read
. Para não ter que ler isso duas vezes, vou na linha 38. Vou fazer um list restaurant
. Vou chamar de list restaurant
. Ponto. Ou melhor, igual a context
. Agora sim, ponto read
. E aí eu chamo meu restaurant data
. Lembrando que está vindo do provider. Ponto list restaurant
. Agora sim, eu só preciso usar o where
. Vou fazer list restaurant. where
. Where o quê? Onde o meu elemento que está sendo averiguado. Ponto id
. É igual a restaurant widget
. Melhor dizendo, widget.restaurant id
. Ponto.
Agora vou verificar se ele achou alguma coisa. Porque ele pode não achar caso eu passe um id
de um restaurante que não existe. Se carry restaurant is empty
, o que é que eu vou fazer? Vou fazer um not found. Is not found
. Igual a true
. Se não for vazio, agora vou ter que procurar se existe o prato dentro desse restaurante. Então, se não, vou fazer um iterable. Iterable. Iterable. Dish. Carry. Dish
. Igual a list dish
. Ou melhor, igual a... Na verdade, não vou acessar o list restaurant
. Vou acessar o meu carry
. Ponto carry has. first
. Por quê? Porque se ele não está vazio, ele tem um restaurante. E como o id
é único, ele só vai vir um restaurante. Vou pegar o primeiro. E vou olhar a list dishes
, que é uma propriedade, um atributo dos restaurantes. Ele tem uma lista de pratos. E na lista de pratos, agora sim, eu posso fazer o where é. id
. O id
do prato seja igual a widget. dishid
. Show.
Agora tenho que fazer a mesma verificação. Linha 52. Vou puxar um pouquinho pra cima pra ficar melhor de ver. Se carry dish is empty
, not found
também. Is not found
. Igual a true
. Se não for empty
, agora sim, finalmente, posso alimentar meu carry dish
com dish
. Ou melhor, carry
... Não, calma. Confundi aqui. Quero alimentar essas duas coisas. Meu restaurant
, que está nulo, e meu dish
, que está nulo. Então vou, na linha 55, fazer dish.dish
igual a carry dish. first
. E vou fazer também restaurant
igual a carry restaurant rest. first
. No final desse teste todo, vou fazer um set state
.
Daí, agora é fazer aquelas verificações que tínhamos feito na tela de restaurante. Se um restaurante for diferente, ou melhor, for nulo, e o dish
... E não, na verdade. Ou, só precisa que um dos dois seja nulo. Se o restaurante não for válido, não faz sentido mostrar a tela. Se o prato não for válido, não faz sentido mostrar a tela. Então, em qualquer um dos casos, se o restaurante for nulo, ou o dish
for nulo, eu retorno return not found screen
. Agora, se for restaurante for diferente de nulo, e dish for diferente de nulo também, eu preciso que os dois sejam válidos. Agora sim, posso implementar toda a lógica da minha tela, que é a mesma coisa. Vou cortar a linha 73 e vou colar lá embaixo, depois do scaffolding. Ótimo! Só falta uma última coisa, que é caso ainda esteja carregando, certo? Então, nos outros casos, só vou aqui até para ele identar direitinho, mas no final dessa verificação eu posso fazer um return, linha 147, return scaffold body center child circle progress indicator. Só por aquele carregamento que tende a ser muito rápido, mas que pode acontecer, certo? Pode acabar demorando por N motivos.
Agora, precisamos corrigir o que está dando erro na nossa disk screen
. Vamos ver o que está dando o erro aqui. Primeiro, para voltar, eu não vou voltar mais para um... na verdade eu tenho que voltar para o restaurante, só que esse restaurante ele vinha através de pilha, lembra? Eu clicava no meu prato no restaurante e a tela era colocada em cima. Isso não dá independência para a tela de prato, então eu preciso fazer com que o botão de voltar volte para o restaurante que eu carreguei aqui.
Para fazer isso, no title, que está dando erro, ao invés de widget restaurant name, que não faz mais sentido, eu vou fazer restaurant bang, porque eu sei que ele não é nulo, name. E na lógica de voltar, eu vou fazer simplesmente um context.go, passo app-router.restaurant e vou usar da mesma forma que eu cheguei no restaurante, eu também consigo voltar para o restaurante, que é passando o id concatenado aqui. Então, barra, passo por interpolação, não preciso mais, certo? No caso, calma, eu estou usando interpolação e concatenação ao mesmo tempo. Eu vou usar concatenação e aí ele faz a interpolação para mim. Então, eu vou fazer mais e eu vou chamar simplesmente restaurant.id.toString e ele vai sugerir para mim, primeiro, o restaurante, eu preciso botar o bang e eu preciso colocar também a barrinha. E agora, sim, ele sugere a interpolação. Lembrando, eu chamo a minha rota de restaurante, barra, o id do restaurante, que eu já sei qual é. Então, eu vou na linha 79, fazer um conto.substituir com a interpolação. Ótimo! A minha volta já está garantida.
Já onde está dando erro é porque antes o dish vinha pronto, certo? Mas não, agora esse dish não vem do widget, ele já está aqui. Então, baixa na linha 108, bota um bang, tira o widget, bota um bang e funciona. Mesma coisa na linha 114, tira o widget, bota um bang e funciona. Mesma coisa na linha 119, tira o widget, coloca um bang e funciona. Porque isso não está vindo mais de fora, nós que carregamos.
Por último, lá no meu addToBag, para adicionar na sacola, eu vou ter que verificar se esse dish já está carregado. Então, eu vou fazer se, linha 167, se dish diferente de nulo, agora eu posso pegar da linha 170 até a 175, colar aqui dentro e tirar esse widget da linha 170 e simplesmente fazer dish.exclamation, porque ele não é nulo, eu garanto que ele não é nulo. E pronto, refatoramos nossa dishScreen
. Só falta ver onde está dando o erro e consertar.
Então, voltando no meu router, a minha dishScreen
não recebe mais um dish, e sim um dishId. Só verificando na linha 68, estou passando o dishId e o restaurantId. Se eu der um conto B, eu vou ter que ir lá no meu dishWidget, que fica na minha restaurantScreen
e chama a dishScreen
para configurar a rota nova sem usar o extra.
Então, vou procurar por restaurant, restaurant vai ter a pasta widget, que vai ter o dishWidget. Vou fechar o explorador e lá embaixo, na linha 71, quando o widget é clicado, eu estava passando aquele extra, que era aquela gambiarra que nós tínhamos feito, lembra? De passar um map com o dish como valor e o nome como, ou melhor, o dish como chave e o nome como valor. Isso não faz mais sentido para nós. O que vamos passar é uma rota. Então, como é nossa rota? Vou até apagar tudo para nós fazermos do zero. A nossa rota é appRouter, restaurant, lembra? Um prato não existe sem um restaurante, mais barra. Daí, vou fazer tudo com concatenação e aí ele transforma para interpolação depois para nós, porque eu acho que com concatenação que tem a abalíngua, ele fica mais claro o que nós estamos fazendo. Então, botei a barra e o que é que eu vou passar aqui?
Vou passar restaurantId
, mas ele ainda está recebendo um name
da nossa tela e não é um name
que queremos receber, é um id
. Então, por enquanto, vou colocar apenas id
aqui e vou continuar a minha rota. Mais barra, ou melhor, não preciso da barra agora. Por quê? Porque vou chamar a minha rota lá do appRouter
e lembre-se que falamos no início desse curso que é um padrão, a barra sempre está no começo da rota e não no fim. Então, sei que a minha appRouter.dish
já vai ter uma barra para mim aqui, certo? E, por último, posso concatenar aqui o meu dish.id.toString
. Ótimo! Deixe-me apenas corrigir esse restaurantId
que estou recebendo para poder fazer a interpolação.
Então, no meu dish widget
, linha 10 e linha 14, substituo restaurantName
por restaurantId
. E na linha 171, agora, só recapitulando, rota de restaurante, aí, barra, o id
do restaurante, rota de dish
, que sei que já tem a barra, mais o dishId
. Quando converter isso para interpolação, conto o ponto, replace with interpolation (substituir com interpolação), pronto, ele já deixa bonitinho para mim. Apesar de que acho que com interpolação fica mais difícil de ver, com concatenação fica mais claro, mas é a forma indicada usar a interpolação. Ótimo! E aí, o que está faltando aqui? Está faltando substituir, lembre-se que quem chama o dish widget
estava passando um nome e não um id
? Quem é que chama esse dish widget
? É o nosso restaurantScreen
.
Então, vou abrir o meu explorador, conto o B, vou olhar o restaurantScreen
, já estava até dando erro, restaurantScreen
. Vou procurar por onde chamava, passando o name
, linha 85, não é mais o name
, vou passar restaurantId
, restaurant.id
. Vou salvar, o restaurante eu preciso do bang nele, pronto, agora sim, vou salvar, e vamos ver se isso tudo funcionou. Vou reiniciar nossa aplicação, lembrando que ela ainda está no Chrome, e o que esperamos agora? Deixe-me voltar até o começo. O que esperamos? Que consiga entrar num prato de um restaurante, ele gere um URL, e depois eu pego esse URL e sem nenhuma conexão, sem, vindo do nada, vou até usar o navegador anônimo de novo, consigo entrar num prato específico. Vamos ver isso acontecendo. Vou clicar embora, vou clicar, sei lá, Donuts, que acho que não entramos ainda, esses donuts estão muito estranhos, parecem muito uma salada, mas vou clicar no donut de Oreo, entrei, e olha, tá, ele deu algum problema aqui, ele está dando um problema porque ele já diz aqui para mim, não há uma rota para restaurantId
, dish
, e aí já entrou o ID
, faltou a barra, então onde é que encontramos essa barra, onde foi esse problema?
Fui até o nosso dish widget
, sem problemas. Abri o restaurant
, acessei o explorador, fui até a parte widget
de restaurant
, abri o dish widget
e percebi que no trabalho de interpolação que fizemos, faltou, de fato, após a minha rota dish
. Lembre-se, a barra é sempre antes, não depois. Então, após a minha rota dish
, antes de colocar o dishId
, faltou uma barra aqui. Tudo bem, sem problemas.
Então, retornei para o meu donut
. Quando recarreguei, e essa é uma das vantagens de dar independência para a tela, ele simplesmente recarregou esse link e já voltou no meu donut
. Desta vez, escolhi o donut
recheado e veja que maravilha, meu donut
recheado está aqui, minha página está carregada, ela é independente, e ela gerou a rota ali em cima que é compartilhável.
Então, se eu copiar esse link, Ctrl-C
, Ctrl-Shift-N
, abrir uma guia anônima, sem nada carregado da aplicação nessa guia, dar um Ctrl-V
, vamos ver se ele cai direto no donut
recheado. Veja, caiu, que maravilha! E agora, sim, a nossa aplicação está preparada para lidar com Deep Link (Link Profundo).
Nossa aplicação está, mas precisamos apenas fazer algumas configurações a nível de nativo, para que o Android consiga habilitar o uso de Deep Link na nossa aplicação. Mas é código pré-pronto, que a própria documentação já nos fornece. Então, vamos fazer isso.
Vou fechar tudo, vou até ter que parar a aplicação aqui, porque vamos mexer em nativo. No meu router
, só porque está me incomodando esse warning (aviso), vou subir lá em cima e vou tirar a linha 10, Models Dish
, já que não estamos mais lidando com Dish
aqui no roteador. A própria tela tem a capacidade de lidar agora com a solicitação.
Vou abrir meu explorador, clicar nesse símbolo bem pequeno no canto superior direito do explorador, que ele recolhe todas as pastas, e vou entrar na minha pasta Android
. Vou estar fazendo essa configuração para Android, que é o dispositivo que eu tenho aqui, mas se você estiver lidando com a iOS, vou deixar também um link para saber mais com a documentação que explica como fazer esse mesmo trabalho para a iOS.
Então, vou entrar em Android
, vou procurar App
, vou procurar por Source
, SCR
, vou entrar em Main
, e dentro da Main
vou procurar pelo nosso famoso Android Manifest
. Entrei no Android Manifest
, e aqui esse arquivo lida com várias das configurações do nosso aplicativo Android. Lembrando, Flutter roda o Android ali por baixo quando você gera um aplicativo Android, tem muito código nativo ali. Então, às vezes, vamos ter que entrar aqui e fazer configurações específicas, do mesmo jeito seria no iOS, no web e por aí vai.
E quais são as configurações que vamos fazer? Você vai procurar pela tag que fecha Activity
, aqui está na linha 27, mas para você poderia ser outra linha. Mas é sempre onde fecha o Activity
. Antes dela, vou criar um espaço aqui e vou copiar esse código. Sim, vou copiar esse código porque é um código que já é entregue pela Google quando você está lidando com o Deep Link no Flutter. Também tem a mesma configuração lá para o iOS.
Mas só porque copiei, não quer dizer que não vamos entender esse código. Cada uma dessas linhas tem um significado aqui para o Android Manifest
. A linha 27, que é a primeira, está basicamente dizendo: vamos habilitar o Deep Link do Flutter. Então, habilita aí, está valendo o Deep Link do Flutter, show!
Agora, vamos abrir as tags desse Intent Filter
, ou seja, numa tradução livre seria um filtro de intenções que verifica várias coisas que você pode fazer no Android, inclusive essa ideia de eu estar no meu navegador no Android e aí esse navegador pode simplesmente, dependendo do link, abrir uma aplicação. Isso é uma coisa que precisa estar garantida. Então, ele vai dizer que está valendo, true!
O que realmente instrui a abertura da aplicação é a linha 29, essa Action Android Name Intent Action View
. Vamos abrir dizendo que a aplicação pode ser aberta por meio de links. Esses links podem estar entre as linhas 30 e 31, tanto por meio de outros aplicativos, que é o padrão, quanto por meio de um navegador. Se você removesse o navegador, apenas um aplicativo poderia chamar o outro. No entanto, por meio de um navegador, qualquer navegador que você tenha, seja o Chrome, o Safari, ou qualquer outro, se você acessar o meu link, o link do Tech Taste, usando esse aplicativo, mesmo que ele esteja fechado, ele vai abrir e vai usar o conceito de Deep Link (Link Profundo).
Por último, nós configuramos o esquema. Lembram que esse esquema que estamos usando aqui é para teste. Mas o que é o esquema? Você vai definir o protocolo que, no meu caso, eu chamei de MyApp
, apenas para fins didáticos. Mas, no seu caso prático, seria um HTTP ou HTTPS e o host, que é o seu site, o site para o qual você comprou um domínio. Aqui eu criei um techtaste.com
, apenas porque é o nome do nosso aplicativo, mas você colocaria o seu domínio também.
Uma coisa muito importante que não cabe a nós fazer aqui, porque varia de hospedagem para hospedagem, é fazer a configuração no seu domínio também. Porque imagine, se eu fizer uma configuração dessas e colocar o Google aqui, ou algum site bem famoso, eu poderia fazer com que sempre que a pessoa entrasse no Google, ela abrisse minha aplicação. E isso não pode acontecer. Então, a medida de segurança que está na documentação, que vou deixar para você saber mais, é você ir no site onde você hospeda, ou melhor, no provedor onde você hospeda o seu site, e colocar um arquivo de configuração bem específico, que vai conter o código do seu aplicativo, uma assinatura digital e coisas do tipo, para provar que aquele site pertence a você. E só assim o navegador vai abrir o seu aplicativo instalado usando o Deep Link.
Mas, para fins didáticos, estamos usando o MyApp
com o TechTest
. Vou salvar e pronto, é essa configuração que precisamos fazer. E como é que testamos isso? Lembra do que eu falei, eu tinha mencionado que para o meu dispositivo, até pegando ele aqui de volta, eu poderia entrar no Chrome e digitar TechTest.com
. Eu teria que provar que esse caminho é meu, e não é, eu não comprei o domínio do TechTest.com
, estamos apenas usando ele aqui.
Mas é claro que as pessoas desenvolvedoras do Flutter pensaram em uma forma de testarmos o Deep Link sem precisar passar por essa parte, sem comprar um domínio. Vamos usar também um código, que é bem extenso, eu admito, então vou deixar em um "para saber mais". Mas vou abrir aqui um PowerShell, você pode abrir qualquer terminal, dar um bom zoom para conseguirmos ver, e vamos rodar esse comando aqui. Deixa eu só copiá-lo aqui. Vamos falar sobre ele, não se preocupe.
Mas olha, ADB
é uma configuração, é na verdade uma CLI do Android, então várias configurações do Android você faz via esse ADB
. E tem o Shell
, AMStart
, então você vai começar a ler alguma coisa. Tem aquele mesmo Android Intent
e Action View
que passamos ali, isso é o que configura para termos o acesso ao Deep Link. Temos myapp2.//tech.techtest.com
, que é o esquema que tínhamos definido. Perceba bem, vou até deixar aqui para ficar mais claro, então myapp2.//
, poderia ser um http2.//
.
Se fosse um site real, seria algo como techtest.com
, e finalmente temos a nossa rota, restaurant
, que pode ser um ID. Não me lembro a qual restaurante esse ID se refere, nem a qual prato esse ID pertence, mas nós passamos como se fosse aquela rota que estávamos visualizando no Chrome.
A última coisa a fazer é passar o pacote. Qual é o nome do meu pacote? Para descobrir o nome do seu pacote, se estiver usando o projeto base, é claro que será o mesmo nome, mas se estiver fazendo isso para outro projeto, você pressiona Ctrl-B
, entra em Android
e procura por BuildGradle
.
Vamos procurar aqui em BuildGradle
. Deixe-me até colapsar tudo. Android
, e então posso encontrar aqui, BuildGradle
. Onde está? Vou entrar em app
, agora sim, BuildGradle
. Ah, é porque foi adicionado esse KTS
depois, por isso que eu estava procurando e não estava encontrando.
Agora sim, quando você entra em BuildGradle
, dentro de Android
, haverá algo chamado Namespace
, e esse Namespace
é o nome que você está procurando para colocar ali. Então, abra o Android
, entre em app
, BuildGradle
, Android
, já aqui na linha 8. A linha pode variar, é claro, mas depois Namespace
, e você encontra essa informação.
Antes de executar esse código, preciso enviar todas essas mudanças que fiz para o meu dispositivo, porque estávamos executando no Chrome. Então, vou fazer o seguinte: vou desinstalar o meu aplicativo, quero fazer tudo do zero. Vou mudar no meu VSCode de Chrome para o meu dispositivo físico e vou executar. Isso pode demorar um pouco, já que desinstalei, será a minha primeira instalação, então eu já volto.
Começou a carregar aqui, não temos mais aquela noção de links e tal, tudo bem, ótimo. Nós podemos simular que uma pessoa foi ao navegador e digitou o nosso link com esse comando. Vamos ver o que acontece. Vou dar um Enter e vamos lá. Olha que maravilhoso, ele foi direto para o Panda Sushi, sem passar por nenhuma tela, só com a pessoa clicando, ou melhor, inserindo a URL no navegador e rodando. É claro, fizemos isso via terminal, mas se ela fizesse via navegador funcionaria.
Quer ser mais ousado? Deixa eu ser mais ousado. Vou parar de rodar a aplicação, ela já está instalada, ok, eu vou fechar, ela fechou. Vou voltar para a minha página social, só para garantir, vou fechar aqui, está tudo fechado e vou fingir que estou no meu navegador. Vou digitar um link que me mandaram, olha, vê aí, vê se você gosta desse guioza, insira no navegador, e aí eu vou rodar o comando de novo. Vamos ver o que acontece, ele abre a aplicação e ele cai direto, ele carregou o restaurante, ele carregou o prato e mostrou aqui para mim, e funcional. Vou adicionar seis guiozas no carrinho, se eu voltar, ele não vai travar, ele vai voltar para o restaurante, se eu voltar de novo, ele volta para a tela inicial, que coisa maravilhosa.
Mas só os detalhes finais que podem acontecer quando você está lidando com o DeepLink (Link Profundo). O que acontece se a pessoa escrever um link nada a ver, do tipo, vou inserir aqui algo nada a ver, não existe, por exemplo, o caminho restaurante, está em inglês, não existe restaurante. Vou dar um enter, vamos ver, caiu nesse page not found (página não encontrada) meio feio, e que dá um exception (exceção), é porque aquela nossa página not found, ela ainda não está configurada no Google Router para ser a página padrão quando ele não acha o caminho. Beleza, usamos ela lá nas nossas telas, usamos em rotas específicas, mas para qualquer outra situação, o Google Router tem uma rota específica dele, uma página específica dele, que não é a que nós queremos, nós queremos a nossa, bonitinha.
Para fazer isso, na verdade, é tranquilo, na linha 19 do meu router, logo depois da initial, antes do router, deixa sempre o router por último, eu vou procurar aqui por error page builder. Esse error page builder, eu posso simplesmente passar, esse error page builder, como é um page, eu não posso passar simplesmente uma tela direto, é como se fosse o custom transition page que nós fizemos com a animação, só que ao invés de ser esse custom transition page, já que nós não queremos transição, nós podemos passar simplesmente, return material page
, ele até sugeriu, material page
, e agora sim, no material page
, eu passo o nosso not found screen
. Vou salvar, não vai funcionar para nós, simplesmente porque isso não está, isso é mágico para mim, isso não está conectado com o nosso VSCode, ele pegou isso do nada quando eu acessei o link. Vou fechar, vou rodar, e aí nós conseguimos fazer o teste direto com o código atualizado que criamos aqui.
E enquanto ele roda, sabe uma coisa que é interessante, de fazer também aqui no nosso router, é fazer configurações de redirecionamento. Isso é bem interessante quando você usa, por exemplo, autenticação. E aí você começa a pensar porque tinha aquele botão lá no drawer que eu mostrei que era de expirar login. Mas carregou, vamos só ver funcionando, vou abrir o nosso transmissor e vou tentar entrar num caminho que não existe. Entrei, e olha, página não encontrada, agora sim, página não encontrada, não importa o que, isso poderia ser bonitinho, com um símbolo, um alegre e tal, mas passa a nossa identidade visual aqui. Se eu clico em voltar, ele volta para a nossa ROM, show.
Mas o que eu estava falando, se você tem um sistema de autenticação, e tudo tem que rodar autenticado, menos a tela de login, é claro, que serve para autenticar, que no nosso caso aqui, seria mais ou menos como se fosse a tela de splash. Será que tem alguma forma de, sempre que eu mudar de rota, a aplicação verificar se a pessoa ainda está logada ou não, e se não estiver, mandar para a tela de login?
Isso é a forma mais comum de utilizar o redirect
do GoRouter. Após um erro na linha 22, farei um redirect
. Esse redirect
também espera um context
e um state
. Aqui, você poderia fazer a verificação, por exemplo, do Firebase Auth (Autenticação do Firebase), que tem a instância currentUser
. Se o currentUser
for diferente de nulo, a pessoa usuária está logada, se for nulo, está deslogada.
Para facilitar, não vamos precisar instalar o Firebase apenas para ver isso acontecendo. Já temos a lógica de autenticação, que é apenas um true
ou false
, em um provider
no nosso aplicativo. Então, vou fazer bool isUserAuth = provider.auth
. Acredito que posso fazer apenas um read
aqui, context
, não precisa do provider
direto, context.read
. Vou chamar nosso settings provider
.
Se você olhar essa classe, ela é apenas um booleano. Quando eu altero esse booleano, ele notifica quem está ouvindo. Isso está disponível em toda a aplicação via provider
. Vou verificar se está autenticado. Se não estiver autenticado, ou seja, se o isUserAuth
for falso, o que farei? Vou retornar uma tela aqui, Ricardo?
Na verdade, se você olhar o redirect
, ele espera uma string
ou nulo. Por que string
? Porque nossos caminhos, nossas rotas, são sempre string
. Então, passo aqui, splash
. Poderia ser login
, se eu tivesse uma tela de login no texto, eu passaria uma tela de login. Mas como eu não tenho, eu tenho a tela de splash
para mandar a pessoa usuária.
Agora, se eu retornar nulo, return null
, significa que não vou fazer redirecionamento nenhum. Isso significa que esse redirecionamento funciona em qualquer rota da aplicação. Sempre que eu mudar de rota, ele passa primeiro aqui pelo redirecionamento, verifica a minha lógica. Se estiver desautenticado, ele manda para a tela de splash
. Se não estiver, ele retorna nulo e apenas segue o caminho que foi mandado, seja para a tela de home
, para a tela de checkout
, para a tela de um prato, o que for, ele apenas segue.
Vou salvar, vou reiniciar a aplicação e vamos ver isso funcionando na prática. Para você entender, no meu botão de bora
, além de mandar para a tela de home
, ele também faz essa variável setu
. Se você olhar no código, ele muda para setu
. Então, quando eu clico bora
, ele não só manda para a tela de home
, mas ele muda para setu
.
Bom, teoricamente era para ser assim, na minha cabeça no projeto base era assim. Mas o que está acontecendo aqui? Está me contradizendo, está dizendo que não é isso, porque eu estou clicando bora
e ele está voltando para a tela de splash
. Isso significa que a pessoa usuária não está autenticada. Ela tenta chegar na home
, cai no redirect
, vê que não está autenticada e acaba voltando para a tela de splash
.
Então, vou ter que abrir a nossa tela de splash
. Vou abrir lib
, vou abrir UI
, vou abrir splash
, splash screen
. E quando eu for lá embaixo, ele só tem de fato o context go
. O que eu preciso fazer aqui? Antes de chamar a tela de home
, eu vou fazer context.read
, vou pegar meus settings provider
e vou fazer que isUserAuthenticated
seja true
. Isso já é um gatilho para olhar na tela de home
se eu estou deslogando de fato.
Então, já que estou aqui mesmo, vou abrir o explorador, vou clicar na tela de home
, na pasta home
, vou clicar em home screen
. No meu drawer
, aqui eu já estou fazendo, eu tenho tanto um botão que ele só desautentica a pessoa usuária, mas não volta para a tela de splash
automaticamente, e tem um botão que configuramos, que ele desautentica a pessoa usuária, linha 49, e volta para a tela de splash
.
O que essa situação de expirar login simula? Deixa eu abrir as coisas de novo, vamos ver se agora vai. Pronto, agora foi. O que essa situação simula?
Existem servidores nos quais a autenticação tem um tempo determinado. Você pode ficar autenticado por uma hora, um dia, uma semana e assim por diante. Quando esse tempo expira no servidor, você não consegue mais realizar nenhuma ação. Essa é a situação que estamos simulando quando eu clico em "inspirar login".
Antes de clicar, vou tentar acessar um prato que exista. Acredito que este aqui exista. Cheguei no Panda Sushi. É como se eu já estivesse no meu aplicativo, já estivesse com ele autenticado, e eu acessei pelo navegador que alguém compartilhou comigo. Cheguei no guioza do Panda Sushi.
Mas digamos que, por diversas razões, eu desloguei. Cliquei em "inspirar login" e pronto, desloguei. No servidor, por exemplo, eu já não estou mais logado. O que acontece se eu tentar entrar de novo? Será que ele vai para o guioza do Panda Sushi? Vamos ver? Não, ele chegou na nossa tela que poderia ser a tela de login, a tela de criação de conta, mas é a nossa tela de splash que está fazendo esse trabalho para nós.
Veja que coisa incrível, esse redirecionamento é muito poderoso. Você pode usar para diversas outras coisas, mas com certeza esse é o uso que eu vejo mais sendo utilizado: verificar se a pessoa está logada e, se não estiver, redireciona para a tela de login, a tela de criar conta e assim por diante.
Agora, finalmente, temos todas as nossas funcionalidades implementadas. É realmente fascinante para mim como eu consigo simplesmente através de um link entrar em uma parte específica do meu código. Claro, o clímax já passou, você já viu, mas eu consigo entrar em uma parte específica do meu código. Ele não entrou porque? Porque está deslogado. Mas se eu logar, agora sim, eu vou direto com independência entre telas e tudo isso só foi possível graças àquela refatoração que fizemos para tirar os navigators e usar esse pacote chamado GoHalter
, que é também desenvolvido pela equipe Flutter e é o mais recomendado quando estamos lidando com esse tipo de situação de deep link.
E é claro, você pode ver pelas potencialidades dele também, é muito poderoso para várias outras coisas. Flutter Web, por exemplo, eu sou entusiasta, uso bastante o GoHalter
.
Parabenizamos por ter concluído mais um curso de Flutter, especialmente este sobre DeepLink (link profundo) e o uso do pacote Go-Router para permitir que, através de um link, seja possível acessar uma parte específica da aplicação, proporcionando autonomia entre as telas. Esse conhecimento é extremamente importante e não precisamos nos esforçar muito para demonstrar sua relevância.
Considere qualquer aplicação que utilizamos atualmente, seja uma rede social, uma aplicação de restaurantes ou qualquer outra. Elas sempre oferecem uma forma de compartilhar um link para acessar uma parte específica do código ou da documentação, que, por vezes, precisa ser carregada do zero. Quando a aplicação está fechada, ela precisa ser carregada do zero, e, por isso, as telas devem ter autonomia. Com o Go-Router, um pacote criado por quem desenvolveu o Flutter, conseguimos realizar isso de maneira primorosa.
O assunto abordado é realmente interessante. Gostaríamos de lembrar sobre alguns recados finais importantes. Se você chegou até aqui, acompanhou o curso, fez o seu projeto e está empolgado, assim como nós, compartilhe sua experiência usando a hashtag #AprendiNalura. Isso é fundamental para que possamos acompanhar seu progresso e ver como você se sente orgulhoso dos projetos realizados aqui.
Além disso, a avaliação que você deixará ao final do curso é muito importante. Este curso possui um estilo diferente, mais prático, no qual vamos fazendo juntos. É interessante acompanhar esse formato. Por isso, além da avaliação numérica, pedimos que deixe um comentário sobre o que achou do curso e se gostou desse formato. Isso nos ajuda a sempre trazer o melhor conteúdo.
Despedimo-nos por aqui e esperamos encontrá-lo no maravilhoso mundo do Flutter. Até lá!
O curso Flutter: deep linking com GoRouter possui 126 minutos de vídeos, em um total de 10 atividades. Gostou? Conheça nossos outros cursos de Flutter em Mobile, ou leia nossos artigos de Mobile.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.