Neste tutorial iremos desenvolver um aplicativo usando React Native. Este aplicativo terá como principal funcionalidade a autenticação de usuários através do Login do Google, utilizando o Firebase como nosso backend.

React Native é uma framework que nos permite desenvolver aplicativos nativos para Android e iOS usando JavaScript e React. É uma ótima escolha para desenvolvedores que desejam criar aplicativos móveis de alta qualidade com agilidade e eficiência.

Neste tutorial, vamos focar na criação de um sistema de autenticação de usuário. A autenticação é um aspecto crucial de muitos aplicativos modernos, e o Login do Google é uma das maneiras mais populares de realizar essa tarefa.

Para tornar as coisas ainda melhores, vamos integrar nosso aplicativo React Native com o Firebase. O Firebase é uma plataforma de desenvolvimento de aplicativos da Google que fornece uma variedade de serviços, incluindo um sistema de autenticação robusto. Ao usar o Firebase, podemos facilmente configurar e gerenciar a autenticação do usuário em nosso aplicativo.

O que você vai fazer

O que você vai aprender

O que você precisa

O Firebase vai funcionar como back-end da aplicação. Vamos começar criando um projeto através do console do Firebase.

Insira um nome de projeto e pressione para continuar.

Agora você será perguntado se deseja habilitar o Google Analytics para seu projeto. Não habilite e continue.

Clique em Criar Projeto. Agora o Firebase alocará recursos para o projeto.

Quando terminar, pressione Continuar para prosseguir para o painel do projeto. Agora, vamos configurar a autenticação.

Na barra de navegação lateral clique em Criação/Autentication.

e será apresentada a seguinte tela e você deverá clicar em Vamos começar. Isso habilitará o módulo de autenticação:

Vamos utilizar a autenticação do Google. Clique em Google:

Depois, na tela abaixo ative o provedor de login Google e informe o nome do projeto e e-mail:

Salve para finalizar a configuração da autenticação do Google em seu projeto do Firebase.

Neste ponto vamos configurar o firebase para responder a um aplicativo Android. Clique no ícone de engrenagem na barra lateral e vá para as configurações do projeto.

Role a tela até aparecer o seguinte:

Clique no segundo ícone, que representa uma aplicação Android. Forneça um nome de pacote para o aplicativo e clique em Registrar app.

Após você deverá fazer o download do arquivo de configuração:

Ignore todas as próximas etapas.

Vamos criar uma aplicação React native. Primeiro abra um terminal e use os seguintes comandos:

npx create-expo-app nome-da-aplicação
cd nome-da-aplicação
code .

Em seguida, no diretório do projeto, execute o seguinte comando para iniciar um servidor de desenvolvimento a partir do terminal.

npx expo start

Você verá as seguintes informações na tela:

Se o seu ambiente estiver corretamente configurado (este tutorial não inclui a configuração do ambiente de desenvolvimento), pressione a letra a no teclado do seu computador para abrir o app em um emulador Android.

Em alguns instantes o app será executado e teremos então a seguinte tela:

Antes de instalar as bibliotecas necessárias ao projeto suspenda o servidor de desenvolvimento com CTRL + C.

npx expo install @react-native-firebase/app
npx expo install @react-native-google-signin/google-signin
npm install @react-navigation/native @react-navigation/drawer @react-navigation/native-stack @react-native-firebase/auth
npx expo install react-native-screens react-native-safe-area-context react-native-gesture-handler react-native-reanimated

Execute o seguinte comando no terminal na raiz do seu projeto. Quando for perguntado sobre o nome do pacote, responda com o nome fornecido no passo 4 deste tutorial.

npx expo prebuild --clean
  1. O arquivo de configuração google-services.json que obtivemos no passo 4 deve ser copiado para dentro de seu projeto no seguinte local: /android/app/.

  1. Abra o arquivo /android/app/build.gradle e adicione o seguinte código:
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services' // <- Adicione esta linha
  1. Adicione o plugin google-services como uma dependência dentro do arquivo /android/build.gradle:
buildscript {
  dependencies {
    // ... outras dependencias
    classpath ('com.google.gms:google-services:4.4.0')  // <- Adicione esta linha
  }
}
  1. Vamos gerar chaves para configurar o app no Firebase console.
cd android && gradlew signingReport

Isso gera duas chaves. Você precisa copiar as chaves 'SHA1' e 'SHA-256' que pertencem à variante 'debugAndroidTest' indicando o diretório de sua aplicação.

Em seguida, você pode adicionar essas chaves às "impressões digitais do certificado SHA" em seu aplicativo no Firebase console. Veja sequencia de imagens a seguir:

Role a página até aparecer o seu aplicativo Android

Copie as chaves geradas no campo Impressão digital do certificado.

  1. Edite o arquivo app.json
{
  "expo": {
    "android": {
      "googleServicesFile": "./android/app/google-services.json" // <- Adicione esta linha
    },
    
    "plugins": [ // <- Adicione estas linhas
      "@react-native-firebase/app",
      "@react-native-google-signin/google-signin"
    ] // <- até aqui
  }
}
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: ["react-native-reanimated/plugin"],
  };
};

Para executar o App temos que usar o seguinte comando:

npx expo run:android

Para limpar o cache:

npx expo run:android --no-build-cache

Um fluxo de autenticação em um mobile app é um processo que permite ao usuário acessar recursos protegidos do aplicativo, como dados pessoais, configurações ou funcionalidades restritas.

Para implementar um fluxo de autenticação em um app feito com React Native, é preciso usar bibliotecas específicas que facilitam a integração com serviços de autenticação externos, como Firebase, Auth0 ou AWS Amplify. Essas bibliotecas fornecem componentes e funções que permitem criar telas de login, cadastro, recuperação de senha e verificação de e-mail, além de gerenciar tokens, sessões e permissões de acesso. Alguns exemplos de bibliotecas que podem ser usadas para criar um fluxo de autenticação em um app React Native são: react-native-firebase, react-native-amplify e react-navigation.

Fluxo de autenticação

A renderização condicional é a técnica mais indicada para construir uma lógica que protege as telas do app com acesso restrito. Considere o código abaixo:

{isAuthenticated
    ? (<HomeScreen />)
    : (<LoginScreen />)
}

Quando a variável `isAuthenticated` é verdadeira, o React Navigation exibe apenas a tela HomeScreen. Quando é falsa, exibe a tela LoginScreen. Isso impede a navegação para a tela protegidas quando o usuário não está logado, e para a tela de login quando está logado. Esse padrão é conhecido como Rotas protegidas.

As telas que requerem login estão "protegidas" e não podem ser acessadas se o usuário não estiver logado. Quando o valor de `isAuthenticated` muda, o comportamento do React Navigation também muda.

Quando usamos este padrão podemos implementar o fluxo de autenticação de forma simples, sem lógica adicional para garantir que a tela correta seja exibida.

Desta foram o código completo do app fica assim:

Arquivo App.js

import { StyleSheet, Text, View, Button } from "react-native";
import { useState } from "react";

// Telas
const LoginScreen = ({ login }) => {
  return (
    <View style={styles.layout}>
      <Text style={styles.title}>Login</Text>
      <Button title="Entrar" onPress={() => login(true)} />
    </View>
  );
};

const HomeScreen = ({ login }) => (
  <View style={styles.layout}>
    <Text style={styles.title}>Home</Text>
    <Button title="Sair" onPress={() => login(false)} />
  </View>
);

const App = () => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  return (
    <View style={styles.container}>
      {isAuthenticated
        ? <HomeScreen login={setIsAuthenticated} />
        : <LoginScreen login={setIsAuthenticated} />}
    </View>
  );
};

export default App;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  layout: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  title: {
    fontSize: 32,
    marginBottom: 16,
  },
});

O trabalho de configuração e instalação de bibliotecas foi feito nos passos 6, 7 e 8 deste codelab. Desta foram podemos nos concentrar agora no código necessário para implem entar a autenticação de usuário utilizando uma conta Google.

Foram adicionadas as funções onLogin e onLogout e também adicionamos um código para configurar o objeto GoogleSignin.

Para obter o valor do atributo webClientId do objeto GoogleSigin, você deve consultar o arquivo google-services.json (conforme mencionado no passo 8). Neste arquivo, você encontrará o valor desejado navegando pela estrutura de chaves da seguinte maneira: "client" -> "oauth_client" -> "client_id". Certifique-se de que o "client_type" associado a este "client_id" seja 3.

import { StyleSheet, Text, View, Button, ActivityIndicator } from "react-native";
import { useState } from "react";
import { GoogleSignin } from "@react-native-google-signin/google-signin";

//funções de autenticação
export const onLogin = async () => {
  const user = await GoogleSignin.signIn();
  return user;
};

export const onLogout = () => {
  GoogleSignin.signOut();
};

GoogleSignin.configure({
  webClientId: "676797397237-pjipptjgb1nuaomvcm6rd6dpt19jl06n.apps.googleusercontent.com",
});

// Telas
const LoginScreen = ({ login }) => {
  const [isSigninInProgress, setIsSigninInProgress] = useState(false);

  return (
    <View style={styles.layout}>
      {isSigninInProgress && <ActivityIndicator />}
      <Text style={styles.title}>Login</Text>
      <Button
        title="entrar"
        onPress={() => {
          setIsSigninInProgress(true);
          onLogin().then((user) => {
            console.log(user);
            login(true);
          });
        }}
      />
    </View>
  );
};

const HomeScreen = ({ login }) => (
  <View style={styles.layout}>
    <Text style={styles.title}>Home</Text>
    <Button title="Sair" onPress={() => onLogout().then(() => login(false))} />
  </View>
);

const App = () => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  return (
    <View style={styles.container}>{isAuthenticated ? <HomeScreen login={setIsAuthenticated} /> : <LoginScreen login={setIsAuthenticated} />}</View>
  );
};

export default App;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  layout: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  title: {
    fontSize: 32,
    marginBottom: 16,
  },
});

A função onLogin faz o seguinte:

  1. Chama a função GoogleSignin.signIn(), que inicia o processo de login do Google. Esta função retorna um objeto promise que resolve para um objeto `user` quando o login é bem-sucedido.
  2. A palavra-chave await é usada para pausar a execução da função `onLogin` até que a promessa seja resolvida. Isso significa que a função onLogin retorna o objeto user somente depois que o login do Google é concluído.
  3. 3. O objeto user contém informações sobre o usuário que fez login, como nome de usuário, e-mail, foto de perfil, etc.

Portanto, você pode usar esta função onLogin para iniciar o processo de login do Google e obter informações sobre o usuário que fez login. Por ser uma função assíncrona, você deve usar a palavra-chave `await` ao chamar `onLogin` ou usar a função dentro de uma cadeia de promessas com `.then()` e `.catch()`.

Quando o usuário clica no botão é acionada a função em onPress que faz o seguinte:

  1. setIsSigninInProgress(true): Altera o estado da variável para indicar que o processo de login está em andamento e então a tela mostra um
  2. onLogin().then((user) => {...}): Aqui, a função onLogin() é chamada. Como onLogin() é uma função assíncrona que retorna uma promessa (objeto javascript do tipo Promise), .then() é usado para especificar o que deve acontecer quando a promessa for resolvida. A função dentro de then() recebe o valor resolvido da promessa (neste caso, um objeto de usuário) como seu argumento.
  3. console.log(user): Isso registra as informações do usuário no console. Isso é útil para fins de depuração.
  4. login(true): Altera o estado de login do usuário para "true", indicando que o usuário está logado.

Vamos trocar o nome da variável de estado que estávamos usando para controlar se o usuário estava logado, que antes chamávamos de isAuthenticated, para user.

Quando o usuário realizar o login, devemos agora armazenar o objeto retornado, e assim o novo código do componente LoginScreen fica assim:

const LoginScreen = ({ login }) => {
  const [isSigninInProgress, setIsSigninInProgress] = useState(false);

  return (
    <View style={styles.layout}>
      {isSigninInProgress && <ActivityIndicator />}
      <Text style={styles.title}>Login</Text>
      <Button
        title="entrar"
        onPress={() => {
          setIsSigninInProgress(true);
          onLogin().then((user) => {
            console.log(user);
            login(user);
          });
        }}
      />
    </View>
  );
};

Considerando que este componente será chamada assim

Vamos analisar o que cada parte deste código faz:

- const LoginScreen = ({ login }) => {...}: Esta é a declaração do componente LoginScreen. Ele recebe login como uma propriedade.

- const [isSigninInProgress, setIsSigninInProgress] = useState(false): Isso é um hook de estado do React. Ele declara uma variável de estado `isSigninInProgress` e uma função para atualizá-la `setIsSigninInProgress`. O estado inicial é `false`, indicando que o processo de login não está em andamento.

- {isSigninInProgress && <ActivityIndicator />}: Renderiza um componente ActivityIndicator se isSigninInProgress for `true`. Isso pode ser usado para mostrar um indicador de carregamento enquanto o login está em andamento.

- <Button ... />: Este é um botão que, quando pressionado, inicia o processo de login. Ele primeiro define `isSigninInProgress` para `true`, depois chama a função `onLogin()`. Quando `onLogin()` é resolvido, ele registra o usuário no console e chama a função `login` com o usuário como argumento.

Quando a função login é chamada dentro de LoginScreen (por exemplo, após um usuário fazer login com sucesso), ela na verdade está chamando a função setUser com o objeto do usuário como argumento. Isso atualiza o estado da variável user do componente App para refletir que um usuário fez login com sucesso.

Para demonstrar o uso dos dados do usuário logado, o componente <HomeScreen> foi modificado para:

const HomeScreen = ({ user, login }) => (
  <View style={styles.layout}>
    <Text style={styles.title}>Home</Text>
    <Image
      style={{ width: 300, height: 300 }}
      source={{
        uri: user.user.photo,
      }}
    />
    <Button title="Sair" onPress={() => onLogout().then(() => login(false))} />
  </View>
);

E o código completo fica assim:

import { StyleSheet, Text, View, Button, ActivityIndicator, Image } from "react-native";
import { useState } from "react";
import { GoogleSignin } from "@react-native-google-signin/google-signin";

//funções de autenticação
export const onLogin = async () => {
  const user = await GoogleSignin.signIn();
  return user;
};

export const onLogout = async () => {
  return await GoogleSignin.signOut();
};

GoogleSignin.configure({
  webClientId: "676797397237-pjipptjgb1nuaomvcm6rd6dpt19jl06n.apps.googleusercontent.com",
});

// Telas
const LoginScreen = ({ login }) => {
  const [isSigninInProgress, setIsSigninInProgress] = useState(false);

  return (
    <View style={styles.layout}>
      {isSigninInProgress && <ActivityIndicator />}
      <Text style={styles.title}>Login</Text>
      <Button
        title="entrar"
        onPress={() => {
          setIsSigninInProgress(true);
          onLogin().then((user) => {
            console.log(user);
            login(user);
          });
        }}
      />
    </View>
  );
};

const HomeScreen = ({ user, login }) => (
  <View style={styles.layout}>
    <Text style={styles.title}>Home</Text>
    <Image
      style={{ width: 300, height: 300 }}
      source={{
        uri: user.user.photo,
      }}
    />
    <Button title="Sair" onPress={() => onLogout().then(() => login(false))} />
  </View>
);

const App = () => {
  const [user, setUser] = useState(false);
  return <View style={styles.container}>{user ? <HomeScreen user={user} login={setUser} /> : <LoginScreen login={setUser} />}</View>;
};

export default App;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  layout: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  title: {
    fontSize: 32,
    marginBottom: 16,
  },
});