React native e firebase

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/Autenticação.

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

Vamos utilizar a autenticação do Google. Clique em Google e forneça o seu email. Veja abaixo como ficará a tela:

Você configurou com sucesso a autenticação do Google em seu projeto do Firebase.

Em uma etapa posterior vamos precisar da configuração associada a um app criado no console do Firebase. 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 para o aplicativo e clique em Registrar aplicativo.

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

O seu arquivo google-services.json será parecido com este:

{
  "project_info": {
    "project_number": "184594998406",
    "project_id": "lista-de-tarefas-d0615",
    "storage_bucket": "lista-de-tarefas-d0615.appspot.com"
  },
  "client": [
    {
      "client_info": {
        "mobilesdk_app_id": "1:184594998406:android:9df3ac0ca5d0ff788a9d07",
        "android_client_info": {
          "package_name": "com.professorangoti.listadetarefas"
        }
      },
      "oauth_client": [
        {
          "client_id": "184594998406-d9fmffc8tj8dgcev6qlm29q5e9issrtc.apps.googleusercontent.com",
          "client_type": 3
        }
      ],
      "api_key": [
        {
          "current_key": "AIzaSyBCsJYBXCRdm35_JpeKFHAUdP5yGIKcVJ8"
        }
      ],
      "services": {
        "appinvite_service": {
          "other_platform_oauth_client": [
            {
              "client_id": "184594998406-d9fmffc8tj8dgcev6qlm29q5e9issrtc.apps.googleusercontent.com",
              "client_type": 3
            }
          ]
        }
      }
    }
  ],
  "configuration_version": "1"
}

Ignore todas as outras etapas, faremos isso manualmente.

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

npx react-native init <nome da aplicação>

Precisamos renomear o pacote dentro da aplicação Android. Use o mesmo nome do pacote do Android informado no passo 2. Execute o seguinte comando dentro da pasta raiz do projeto:

cd <nome da aplicação>
npx react-native-rename "nome da aplicação" -b <nome do pacote>

O último comando abrirá o Visual Studio Code nesse diretório. Vamos limpar o código do arquivo App.js, que ficará assim:

import React from 'react';
import {Header} from 'react-native/Libraries/NewAppScreen';

function App() {
  return <Header />;
}

export default App;

Vamos criar agora um arquivo na raiz do projeto com o nome jsconfig.json, com o seguinte código:

{
  "compilerOptions": {
    "checkJs": true,
    "jsx": "react-native",    
  },
  "exclude": ["node_modules", "**/node_modules/*"]
}

Em seguida, execute o seguinte comando para iniciar o aplicativo:

npx react-native run-android

O seu aplicativo exibirá a tela abaixo. Observe no canto superior direito da tela a mensagem de ativação do Hermes.

  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:
dependencies {
    ...
    
    // For animated GIF support
    implementation 'com.facebook.fresco:animated-gif:2.6.0'
    
    ...
}

// Adicione esta linha no fim do arquivo
apply plugin: 'com.google.gms.google-services' // <--- this should be the last line
  1. Adicione o plugin google-services como uma dependência dentro do arquivo /android/build.gradle:
buildscript {
    ext {
        buildToolsVersion = "..."
        minSdkVersion = ...
        compileSdkVersion = ...
        targetSdkVersion = ...
        supportLibVersion = "..."
        googlePlayServicesAuthVersion = "19.2.0" // <--- use this version or newer
    }
        
    dependencies {
        ...
        classpath('com.google.gms:google-services:4.3.10') // <--- use this version or newer
        ...
    }
  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' e ‘Store' 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.

yarn add @react-native-firebase/app @react-native-firebase/auth @react-native-firebase/firestore @react-native-firebase/storage @react-native-firebase/messaging
yarn add @react-native-google-signin/google-signin
yarn add @react-navigation/native react-native-screens react-native-safe-area-context @react-navigation/native-stack @react-navigation/bottom-tabs
yarn add react-native-paper
yarn add react-native-vector-icons

Edite o arquivo android/app/build.gradle ( Não é android/build.gradle ) e adicione o seguinte:

apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
yarn add react-native-encrypted-storage

Vamos crias as seguintes pastas a partir da raiz do projeto:

E depois teremos a seguinte estrutura no nosso projeto

Normalmente o fluxo segue a sequência:

Vamos usar a renderização condicional para organizar o fluxo de autenticação:

<NavigationContainer>
    <Stack.Navigator>
        {state.isLoading ? (
          <Stack.Screen name="Splash">
            {props => <SplashScreen {...props} loaded={loaded} />}
          </Stack.Screen>
        ) : state.userToken == null ? (
          // Usuário não autenticado
          <Stack.Screen name="SignIn">
            {props => <SignInScreen {...props} sigIn={signIn} />}
          </Stack.Screen>
        ) : (
          // Usuário autenticado
          <Stack.Screen name="Home">
            {props => <HomeScreen {...props} signOut={signOut} />}
          </Stack.Screen>
        )}
    </Stack.Navigator>
</NavigationContainer>

Vamos usar duas variáveis para controlar a autenticação:

isLoading indica que o sistema está carregando o token de usuário armazenado no dispositivo ou recuperando o usuário.

userToken armazena o token do usuário. Se é nulo indica que o usuário não está autenticado.

Estas variáveis serão parte do estado que a aplicação controla e temos também as funções que fazem a transição entre os estados possíveis. Abaixo p código que define o estado e as funções correspondentes.

const initialState = {
    isLoading: true,
    userToken: null,
  };
  const [state, setState] = useState(initialState);

  const loaded = data => {
    if (data === '') {
      setState(prev => {
        return {...prev, isLoading: false};
      });
    } else {
      setState(prev => {
        return {isLoading: false, userToken: data};
      });
    }
  };

  const signIn = data => {
    setState(prev => {
      return {...prev, userToken: data};
    });
  };

  const signOut = () => {
    setState(prev => {
      return {isLoading: true, userToken: null};
    });
  };

O código final do arquivo App.js fica assim:

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createContext, useState } from 'react';
import SplashScreen from './src/screens/SplashScreen';
import SignInScreen from './src/screens/SignInScreen';
import HomeScreen from './src/screens/HomeScreen';

// @ts-ignore
export const AuthContext = createContext();

const Stack = createNativeStackNavigator();

export default function App() {
  const initialState = {
    isLoading: true,
    userToken: null,
  };
  const [state, setState] = useState(initialState);

  const loaded = data => {
    if (data === '') {
      setState(prev => {
        return { ...prev, isLoading: false };
      });
    } else {
      setState(prev => {
        return { isLoading: false, userToken: data };
      });
    }
  };

  const signIn = data => {
    setState(prev => {
      return { ...prev, userToken: data };
    });
  };

  const signOut = () => {
    setState(prev => {
      return { isLoading: true, userToken: null };
    });
  };

  return (
    <AuthContext.Provider value={{ state }}>
      <NavigationContainer>
        <Stack.Navigator>
          {state.isLoading ? (
            <Stack.Screen name="Splash">
              {props => <SplashScreen {...props} loaded={loaded} />}
            </Stack.Screen>
          ) : state.userToken == null ? (
            // Usuário não autenticado
            <Stack.Screen name="SignIn">
              {props => <SignInScreen {...props} signIn={signIn} />}
            </Stack.Screen>
          ) : (
            // Usuário autenticado
            <Stack.Screen name="Home">
              {props => <HomeScreen {...props} signOut={signOut} />}
            </Stack.Screen>
          )}
        </Stack.Navigator>
      </NavigationContainer>
    </AuthContext.Provider>
  );
}

O valor a ser usado na configuração GoogleSignin.configure você vai encontrar no console do Firebase:

import auth from '@react-native-firebase/auth';
import {
  GoogleSignin,
  GoogleSigninButton,
} from '@react-native-google-signin/google-signin';
import {useState} from 'react';
import {View} from 'react-native';
import {styles} from '../styles/styles';

GoogleSignin.configure({
  webClientId:
    '829208944695-43u8nvtkbtnufrm6pto7r7pk4do2u4a4.apps.googleusercontent.com',
});

export const logOut = async () => {
  try {
    await GoogleSignin.signOut();
  } catch (error) {
    console.error(error);
  }
};

export const getCurrentUserInfo = async () => {
  try {
    const userInfo = await GoogleSignin.signInSilently();
    return userInfo;
  } catch (error) {
    console.log(error);
  }
};

function SignInScreen({signIn}) {
  const [isSigninInProgress, setIsSigninInProgress] = useState(false);

  async function onGoogleButtonPress() {
    setIsSigninInProgress(true);
    // Get the users ID token
    const {idToken} = await GoogleSignin.signIn();

    // Create a Google credential with the token
    const googleCredential = auth.GoogleAuthProvider.credential(idToken);

    // Sign-in the user with the credential
    return auth().signInWithCredential(googleCredential);
  }

  return (
    <View style={styles.container}>
      <GoogleSigninButton
        style={styles.button}
        size={GoogleSigninButton.Size.Icon}
        color={GoogleSigninButton.Color.Light}
        onPress={() => onGoogleButtonPress().then(user => signIn(user.user))}
        disabled={isSigninInProgress}
      />
    </View>
  );
}

export default SignInScreen;

Estrutura do objeto do usuário:

{"displayName": "Edson Angoti Junior", "email": "angoti@iftm.edu.br", "emailVerified": true, "isAnonymous": false, "metadata": {"creationTime": 1668626580750, "lastSignInTime": 1668626775914}, "multiFactor": {"enrolledFactors": [Array]}, "phoneNumber": null, "photoURL": "https://lh3.googleusercontent.com/a/ALm5wu0IJSCEz1Jb5RobScLVVfG3W-le4_WaoFL0xrhnUg=s96-c", "providerData": [[Object]], "providerId": "firebase", "tenantId": null, "uid": "wcwLaOIek1Tpk2xYWfxzVcyP07F3"}
import {useContext} from 'react';
import {AuthContext} from '../../App';

const {View, Text, Button, Image} = require('react-native');
const {styles} = require('../styles/styles');

const HomeScreen = ({signOut}) => {
  const {user} = useContext(AuthContext);
  return (
    <View style={styles.container}>
      <Text style={styles.texto}>Tela principal</Text>
      <Text style={styles.texto}>{user.displayName}</Text>
      <Image
        source={{uri: user.photoURL}}
        style={{width: 200, height: 350}}
        resizeMode="contain"
      />
      <Button title="Sair" onPress={() => signOut()} />
    </View>
  );
};

export default HomeScreen;
import {ActivityIndicator, View} from 'react-native';
import {styles} from '../styles/styles';
import {getCurrentUserInfo} from './SignInScreen';

const SplashScreen = ({loaded}) => {
  getCurrentUserInfo().then(user => {
    loaded(user);
  });

  return (
    <View style={styles.container}>
      <ActivityIndicator size="large" />
    </View>
  );
};

export default SplashScreen;