O desenvolvimento para aplicativos mobile tem visto uma evolução notável nos últimos anos. À medida que a tecnologia avança, a necessidade de criar aplicativos mais interativos, eficientes e facilmente acessíveis torna-se primordial.

Enquanto existem diversas formas de criar um aplicativo móvel com as evoluções tecnológicas no ramo, uma das mais populares é o Expo: um framework open-source que facilita a construção de aplicativos com React Native, utilizando Javascript/Typescript.

O Expo simplifica (e muito!) o processo de desenvolvimento removendo ou diminuindo a necessidade de configurações nativas, permitindo que a pessoa desenvolvedora consiga focar em, de fato, criar novas funcionalidades e melhorias para a sua aplicação. É uma ótima escolha, principalmente, para profissionais que estão começando no mundo mobile ou, simplesmente, para ter uma experiência de desenvolvimento mais rápida.

Uma das partes mais importantes de qualquer aplicação é a autenticação, ou seja, o fluxo de login que irá permitir à pessoa usuária navegar ou não para certas telas de uma aplicação.

Tradicionalmente, quando falamos de autenticação com o Expo, também estamos falando do React Navigation, que é a forma padrão de lidar com navegação no React Native.

No entanto, apesar de ser uma solução robusta para gerenciamento de rotas, à medida que seu aplicativo vai crescendo ao utilizar essa abordagem tradicional, você pode se deparar com processos manuais que precisam de muitas linhas de código para serem feitos.

Com o objetivo de minimizar a complexidade e esforço manual que vêm na configuração de rotas com o React Navigation, o time por trás do Expo lançou uma novidade: Expo Router, uma alternativa para roteamento e navegação com React Native.

Principais funcionalidades do Expo Router

Nativo

Construído em cima do React Navigation, a navegação do Expo Router é nativa. Assim, ao navegar no app, as pessoas usuárias têm transições suaves e uma experiência intuitiva, características essenciais para aplicativos modernos.

Deep linking automatizado

Com o Expo Router, cada tela do seu aplicativo é imediatamente apta para deep linking. Isso significa que você pode facilmente criar links diretos para qualquer parte do seu aplicativo, como um link em uma notificação push que leva a pessoa usuária diretamente para uma tela específica.

Essa funcionalidade elimina a complexidade da configuração manual do deep linking, facilitando o compartilhamento de rotas do seu aplicativo.

Prioridade ao Offline

Os aplicativos são armazenados em cache e executados de forma offline first, com atualizações automáticas quando você publica uma nova versão.

O Expo Router ainda trás um conceito de file based routing, ou seja, as rotas da aplicação refletem a estrutura dos arquivos criados na pasta /app, a pasta principal do projeto que irá englobar todas as telas. Parecido, por exemplo, com a estrutura do Next.js.

Quando um arquivo é criado na pasta /app, automaticamente se torna uma rota no app,  já públicas e disponíveis para navegação.

Além disso, as navegações para diferentes telas podem ser feitas usando um elemento <Link href=’/nome-da-rota’, por exemplo, bem parecido com o que fazemos na web. Você pode usar um router.replace(‘nome-da-rota’) para definir globalmente o redirecionamento de rota, além de rotas dinâmicas, criação de grupos de rotas e muito mais.

Exemplo de código

Agora, vamos a um exemplo no código! Implementaremos um fluxo de autenticação utilizando o Expo Router para navegação, utilizando Typescript. Para isso, você precisa inicialmente iniciar um projeto com o Expo seguindo os passos diretamente da documentação: https://docs.expo.dev/router/installation/#quick-start

Ou você  também pode clonar o projeto abaixo que contém o código desenvolvido nesse artigo, caso queira reproduzir esse exato exemplo:

https://github.com/lumamontes/expo-router-auth

Vamos analisar a estrutura do projeto, que está de acordo com a documentação oficial do Expo Router:

 

Alguns pontos-chaves para entender o Expo Router:

  • Todos os arquivos que estão na pasta /app serão rotas (com exceção de arquivos “especiais”, que irei comentar abaixo).
  • _layout.tsx: o expo router possibilita a criação de Layout routes, basicamente serão elementos que são compartilhados entre diversas telas, como uma header. No caso do arquivo app/\_layout.tsx, ele é um layout que será compartilhado entre todas as telas dentro da pasta /app.
  • Pastas (auth) e (tags): são grupos de rotas do Expo Router, que são usados para organizar as rotas do app. Você pode adicionar quantos grupos quiser. Grupos também são bons para organizar seções do app.

No exemplo a seguir, temos app/(auth) que terá as telas para pessoas usuárias autenticadas. Um ponto interessante é que os grupos não afetam a url, ou seja, um arquivo localizado na pasta app/(auth)/nome-do-arquivo.tsx não terá a url app/(auth)/nome-do-arquivo, mas sim app/nome-do-arquivo.

  • – ctx.tsx: é um contexto de autenticação, que irá centralizar algumas informações globais como a sessão da pessoa usuária, a função de logar e deslogar.
  • – +not-found.tsx: é uma rota que será acessada quando nenhuma outra rota for encontrada.

Logo, o nosso app possuirá duas camadas:

  • – Rotas para pessoas usuárias autenticadas (todo o conteúdo do grupo (auth));
  • – Rotas para pessoas usuárias não autenticadas (somente a tela de login);

Para fazer essa separação entre a camada de pessoas usuárias autenticadas e não autenticadas, é possível criar um contexto que vai definir essas regras de negócio.

1. Crie um contexto de autenticação

Esse contexto irá centralizar algumas informações e lógicas globais como a sessão da pessoa usuária, a função de signIn e signOut.

“`

iimport React from “react”;

import { useStorageState } from “./useStorageState”;

const AuthContext = React.createContext<{

signIn: () => void;

signOut: () => void;

session?: string | null;

isLoading: boolean;

}>({

signIn: () => null,

signOut: () => null,

session: null,

isLoading: false,

});

export function useSession() {

const value = React.useContext(AuthContext);

return value;

}

export function SessionProvider(props: React.PropsWithChildren) {

const [[isLoading, session], setSession] = useStorageState(“session”);

return (

<AuthContext.Provider

value={{

signIn: () => {

// Adicione sua lógica de login aqui

// Para fim de exemplos, iremos apenas adicionar uma sessão falsa

setSession(“Joãozinho”);

},

signOut: () => {

setSession(null);

},

session,

isLoading,

}}

>

{props.children}

</AuthContext.Provider>

);

}

“`

A função signIn irá salvar a sessão da pessoa usuária, nesse caso, uma simples string ‘Joãozinho’, e a função signOut irá remover a sessão alterando esse valor para null.

Para finalidade de testes, a função signIn não terá nenhum tipo de validação, sempre que ela for chamada, a pessoa usuária será autenticada. No entanto, em um app real, você pode adicionar a lógica de login do seu app fazendo uma requisição para uma API, por exemplo.

Um ponto importante é que iremos salvar essas informações de sessão da pessoa usuária no storage do dispositivo, para que ela não precise logar toda vez que abrir o app.

Para isso, utilizaremos o hook useStorageState(‘session’), que é formado por três funções:

  • – A função useAsyncState é uma variação personalizada do hook ‘useState’ do React, que iremos usar para gerenciar estados em componentes do React Native. Será usado para guardar um valor inicial, uma função que irá alterar esse valor, e um booleano que irá representar um estado de carregamento desse valor

“`

import * as SecureStore from “expo-secure-store”;

import * as React from “react”;

import { Platform } from “react-native”;

type UseStateHook<T> = [[boolean, T | null], (value: T | null) => void];

function useAsyncState<T>(

initialValue: [boolean, T | null] = [true, null]

): UseStateHook<T> {

return React.useReducer(

(

state: [boolean, T | null],

action: T | null = null

): [boolean, T | null] => [false, action],

initialValue

) as UseStateHook<T>;

}

“`

  • – setStorageItemAsync: que irá salvar ou deletar um item na storage do dispositivo, dependendo do valor passado.

“`

export async function setStorageItemAsync(key: string, value: string | null) {

if (value == null) {

await SecureStore.deleteItemAsync(key);

} else {

await SecureStore.setItemAsync(key, value);

}

}

“`

  • – useStorageState: que irá retornar um estado e uma função para alterar esse estado, salvando e recuperando as informações do storage do dispositivo utilizando a biblioteca expo-secure-store.

“`

export function useStorageState(key: string): UseStateHook<string> {

const [state, setState] = useAsyncState<string>();

React.useEffect(() => {

SecureStore.getItemAsync(key).then((value) => {

setState(value);

});

}, [key]);

const setValue = React.useCallback(

(value: string | null) => {

setState(value);

setStorageItemAsync(key, value);

},

[key]

);

return [state, setValue];

}

“`

Logo, se eu usar o hook dessa forma: useStorageState(‘session’), ele irá retornar: isLoading, session e setSession.

Mas se eu usasse dessa forma: useStorageState(‘user’), ele iria retornar: isLoading, user e setUser, por exemplo.

Podemos, então, entender que o useStorageState é um hook genérico que irá retornar um estado e uma função para alterar esse estado, salvando e recuperando as informações do storage do dispositivo utilizando a biblioteca expo-secure-store.

2. Adicione o arquivo app/\_layout.tsx

Esse arquivo é um layout que será compartilhado entre todas as telas do app. Nele, podemos:

2.1 –  Definir a rota inicial do app

“`

export const unstable_settings = {

initialRouteName: “login”,

};

“`

Essa simples linha de código irá garantir que a rota inicial do nosso app será sempre ‘login’, ou seja, o componente presente no arquivo de login.tsx!

2.2 – Adicionar o nosso contexto por volta de todo o seu app

Para fazer com que o nosso aplicativo entenda se a pessoa usuária está logada ou não e mostrar as telas adequadas, vamos envolver todo o aplicativo com o nosso contexto utilizando o componente chamado SessionProvider, que criamos anteriormente no passo 1. Esse contexto vai verificar o estado de login da pessoa usuária.

Para implementar essa lógica de maneira eficaz, podemos usar o elemento <Slot> do Expo Router. O <Slot> é um componente especial que renderiza o conteúdo filho (children) da rota atual.

Portanto, ao colocar <Slot> dentro do SessionProvider, garantimos que o aplicativo exiba a interface apropriada conforme o estado de autenticação da pessoa usuária  — seja a tela de login, se ele não estiver logado, ou as outras partes do app, se ele estiver. Veja como isso é feito:

“`

import { Slot } from “expo-router”;

import { SessionProvider } from “./ctx”;

export default function RootLayoutNav() {

return (

<SessionProvider>

<Slot />

</SessionProvider>

);

}

“`

3. Crie a tela de login

Na tela de login, que será o arquivo app/login.tsx, você pode adicionar a lógica de login do seu app, como um formulário de email e senha. No nosso caso, temos somente um botão que irá logar a pessoa usuária no app diretamente, sem nenhuma validação por ora.

“`

export default function Login() {

const { signIn } = useSession();

const handleLogin = () => {

signIn();

//Antes de navegar, tenha certeza de que a pessoa usuária está autenticada

router.replace(“/”);

};

return (

<View>

<Button title=”Login” onPress={handleLogin} />

</View>

);

}

“`

Isso irá redirecionar para (auth)/\_layout.tsx!

4. Adicione a lógica de autenticação no arquivo app/(auth)/\_layout.tsx

Esse arquivo é um layout que será compartilhado entre todas as telas do app no grupo (auth), ou seja, todas as telas que precisam de autenticação para serem acessadas. Nele, podemos adicionar a lógica de autenticação do app:

  • Se a pessoa usuária não possuir sessão, redirecionar para a tela de login;
  • Caso a pessoa usuária tenha sessão, retornar as demais telas do app.

“`

const { session } = useSession();

if (!session) {

// Se a pessoa usuária não tiver sessão, redirecionar para a tela de login

return <Redirect href=”/login” />;

}

return (

<Stack>

<Stack.Screen name=”(tabs)” options={{ headerShown: false }} />

<Stack.Screen name=”modal” options={{ presentation: “modal” }} />

</Stack>

);

“`

Assim, ao clicar no botão para fazer login e ser redirecionado com sucesso, a pessoa usuária terá acesso à tela app/(auth)/(tabs)/index.tsx!

E caso a pessoa usuária não esteja autenticada, ela será redirecionada para a tela de login, completando um fluxo básico de autenticação.

Conclusão

Fazendo um comparativo com React Navigation, acredito que o Expo Router é bem acessível e fácil de entender, principalmente para quem vem do desenvolvimento de front-end web.

No React Navigation, várias funcionalidades eram feitas de forma manual, como a criação das rotas, a configuração de deep linking, entre outras, e esses processos superimportantes são feitos já automaticamente pelo Expo Router, deixando o código bem mais limpo.

Pelo fato de ser file based routing, a estrutura do projeto fica bem mais organizada, e a navegação entre telas é bem mais simples, com a utilização do elemento <Link href=’/nome-da-rota’.

No geral, acredito que o Expo Router é uma ótima ferramenta para quem está começando a desenvolver apps com react native, ou para quem já tem experiência com react native e quer otimizar o tempo de desenvolvimento e manter uma navegação de fácil implementação, e utilizarei nos meus novos projetos com Expo.

Artigo escrito por Luma Montes, da Comunidade PrograMaria.