React native e firebase
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.
O Cloud Firestore é um banco de dados não relacional (NoSQL), assim como o MongoDB. Para ativar o Cloud Firestore, clique em Firestore Database na barra lateral:
Clique em Criar banco de dados
e você verá a seguinte tela:
Vamos iniciar o banco de dados do Firestore no modo de teste. Isso ocorre porque não queremos nos preocupar com um ambiente de produção e regras de segurança, para nos concentrarmos mais no lado do desenvolvimento. No entanto, você pode alterá-lo para o modo de produção depois de concluir o projeto.
Clique em Avançar e escolha um local geográfico onde ficará o seu banco de dados (pode ser qualquer um) e pressione Criar. Isso criará seu banco de dados do Cloud Firestore.
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.
O Firebase Storage será usado para armazenar arquivos da aplicação. Clique em primeiros passos para começar.
Na primeira etapa use as configurações mostradas abaixo:
Escolha um local do servidor e aperte o botão Concluir para finalizar a configuração.
Verifique as regras de segurança do Storage e compare a regra mostrada abaixo. Modifique sua regra se estiver diferente do mostrado abaixo.
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/*"]
}
Hermes é uma máquina virtual Javascript otimizada para React Native. Para usar Hermes você precisa abrir o arquivo android/app/build.gradle
e fazer a alteração mostrada abaixo:
project.ext.react = [
entryFile: "index.js",
enableHermes: true
]
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.
google-services.json
que obtivemos no passo 4 deve ser copiado para dentro de seu projeto no seguinte local: /android/app/./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
/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
...
}
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-image-picker
yarn add react-native-encrypted-storage
yarn add @react-native-firebase/messaging
Todos módulos em um só comando:
yarn add @react-native-firebase/app @react-native-firebase/auth @react-native-firebase/firestore @react-native-firebase/storage @react-native-firebase/messaging @react-native-google-signin/google-signin @react-navigation/native react-native-screens @react-navigation/bottom-tabs react-native-safe-area-context @react-navigation/native-stack react-native-paper react-native-vector-icons react-native-image-picker react-native-encrypted-storage @react-native-firebase/messaging
Vamos crias as seguintes pastas a partir da raiz do projeto:
E depois teremos a seguinte estrutura no nosso projeto
import React, { createContext, useEffect, useMemo, useReducer } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './src/screens/HomeScreen';
import SignInScreen, { logOut } from './src/screens/SignInScreen';
import SplashScreen from './src/screens/SplashScreen';
import { Text, TouchableOpacity } from 'react-native';
import { styles } from './src/util/styles';
const Stack = createNativeStackNavigator();
// @ts-ignore
export const AuthContext = createContext();
function App() {
const [state, dispatch] = useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
userToken: null,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
},
);
// temporizador para exibir a splashScreen
useEffect(() => {
const sleep = async (ms) => {
await new Promise((resolve) => setTimeout(resolve, ms));
// @ts-ignore
dispatch({ type: 'RESTORE_TOKEN' });
};
sleep(500);
}, []);
const authContext = useMemo(
() => ({
signIn: async (data) => {
// @ts-ignore
dispatch({ type: 'SIGN_IN', token: data });
},
// @ts-ignore
signOut: () => {
logOut();
// @ts-ignore
dispatch({ type: 'SIGN_OUT' });
},
usuario: state.userToken,
}),
[state.userToken],
);
if (state.isLoading) {
return <SplashScreen />;
}
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<Stack.Navigator>
{state.userToken == null ? (
// No token found, user isn't signed in
<Stack.Screen
name="SignIn"
component={SignInScreen}
options={{
title: 'Sign in',
headerStyle: {
backgroundColor: '#2b008f',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
// When logging out, a pop animation feels intuitive
// You can remove this if you want the default 'push' animation
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
) : (
// User is signed in
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
title: state.userToken.displayName,
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
headerTitleAlign: 'left',
headerRight: () => (
<TouchableOpacity
onPress={() => {
//authContext.usuario.delete();
authContext.signOut();
}}>
<Text style={styles.logoutButton}>Sair</Text>
</TouchableOpacity>
),
}}
/>
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
}
export default App;
import React, { useContext, useEffect, useState } from 'react';
import { FlatList, View } from 'react-native';
import { styles } from '../util/styles';
import firestore from '@react-native-firebase/firestore';
import { Button, TextInput } from 'react-native-paper';
import Mensagem from '../components/Mensagem';
import { AuthContext } from '../../App';
import { launchImageLibrary } from 'react-native-image-picker';
import { saveImageMessage } from '../util/firebase';
const HomeScreen = () => {
const ref = firestore().collection('mensagens');
const [mensagem, setMensagem] = useState('');
// const [loading, setLoading] = useState(true);
const [mensagens, setMensagens] = useState([]);
const { usuario } = useContext(AuthContext);
async function enviarMensagem() {
await ref.add({
texto: mensagem,
usuario: usuario.displayName,
foto: usuario.photoURL,
timeStamp: firestore.FieldValue.serverTimestamp(),
});
setMensagem('');
}
function enviarImagem() {
console.log('escolhendo imagem');
launchImageLibrary({
mediaType: 'photo',
}).then((result) => {
saveImageMessage(result.assets[0], usuario);
})
.catch(error => {
console.log('-------------------------> ' + error);
});
}
useEffect(() => {
console.log('--------------- useEffect <<<<<<<<<<<<');
return ref.orderBy('timeStamp', 'desc').onSnapshot((querySnapshot) => {
const list = [];
querySnapshot.forEach((doc) => {
list.push({
...doc.data(),
id: doc.id,
});
});
setMensagens(list);
// if (loading) {
// setLoading(false);
// }
});
}, []);
const Separador = () => {
return <View style={styles.separador} />;
};
// if (loading) {
// return null;
// }
return (
<>
<FlatList
style={styles.mensagens}
data={mensagens}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
return <Mensagem {...item} />;
}}
ItemSeparatorComponent={Separador}
/>
<View style={styles.containerTextInput}>
<TextInput
label={'Nova mensagem'}
value={mensagem}
onChangeText={setMensagem}
style={styles.textInputStyle}
/>
<Button
icon="send"
onPress={() => enviarMensagem()}
style={styles.buttonTextInput}
/>
<Button
icon="camera"
onPress={() => enviarImagem()}
style={styles.buttonTextInput}
/>
</View>
</>
);
};
export default HomeScreen;
import auth from '@react-native-firebase/auth';
import {
GoogleSignin,
GoogleSigninButton,
} from '@react-native-google-signin/google-signin';
import React, { useContext, useState } from 'react';
import { View } from 'react-native';
import { AuthContext } from '../../App';
import { styles } from '../util/styles';
export async function logOut() {
try {
await GoogleSignin.signOut();
} catch (error) {
console.error(error);
}
}
function SignInScreen() {
const { signIn } = useContext(AuthContext);
const [isSigninInProgress, setIsSigninInProgress] = useState(false);
GoogleSignin.configure({
webClientId:
'439977024970-hl040j52rem8m896hh3ta4r1fqejakkd.apps.googleusercontent.com',
});
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.Wide}
color={GoogleSigninButton.Color.Light}
onPress={() => onGoogleButtonPress().then((user) => signIn(user.user))}
disabled={isSigninInProgress}
/>
</View>
);
}
export default SignInScreen;
import React from 'react';
import { View, Text, Image } from 'react-native';
import { styles } from '../util/styles';
const SplashScreen = () => {
const LOADING_IMAGE_URL = 'https://www.google.com/images/spin-32.gif?a';
return (
<View style={styles.container}>
<Text style={styles.titulo}>Projeto X</Text>
<Image source={{ uri: LOADING_IMAGE_URL }} style={styles.splashLogo} />
</View>
);
};
export default SplashScreen;
import React from 'react';
import { View, Image } from 'react-native';
import { Avatar, List } from 'react-native-paper';
import { styles } from '../util/styles';
function Mensagem({ texto, usuario, foto, imageUrl }) {
return (
<View>
<List.Item
title={usuario + ' falou'}
titleStyle={{ fontSize: 10 }}
description={texto}
descriptionStyle={{ fontSize: 16 }}
left={() => (
<Avatar.Image
size={44}
source={{
uri: foto,
}}
/>
)}
/>
{imageUrl ? (
<Image
source={{ uri: imageUrl }}
style={styles.imageMessage}
resizeMode="contain"
/>
) : (
<></>
)}
</View>
);
}
export default React.memo(Mensagem);
import firestore from '@react-native-firebase/firestore';
import storage from '@react-native-firebase/storage';
var LOADING_IMAGE_URL = 'https://www.google.com/images/spin-32.gif?a';
export async function saveImageMessage(file, usuario) {
const ref = firestore().collection('mensagens');
try {
// 1 - We add a message with a loading icon that will get updated with the shared image.
const messageRef = await ref.add({
usuario: usuario.displayName,
foto: usuario.photoURL,
timeStamp: firestore.FieldValue.serverTimestamp(),
imageUrl: LOADING_IMAGE_URL,
});
// 2 - Upload the image to Cloud Storage.
// const filePath = `${usuario.uid}/${messageRef.id}/${file.fileName}`;
const filePath = `${file.fileName}`;
const newImageRef = storage().ref(filePath);
const task = newImageRef.putFile(file.uri);
task.on('state_changed', (taskSnapshot) => {
console.log(
`${taskSnapshot.bytesTransferred} transferred out of ${taskSnapshot.totalBytes}`,
);
});
task.then(() => {
console.log('Image uploaded to the bucket!');
// 3 - Generate a public URL for the file.
newImageRef.getDownloadURL().then((publicImageUrl) => {
console.log('------------> url da imagem: ' + publicImageUrl);
// 4 - Update the chat message placeholder with the image's URL.
messageRef.update({
imageUrl: publicImageUrl,
});
});
});
} catch (error) {
console.error(
'-----------------> There was an error uploading a file to Cloud Storage:',
error,
);
}
}
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#18073f',
},
navbar: { backgroundColor: '#f4511e' },
titulo: {
fontSize: 20,
fontWeight: 'bold',
padding: 8,
color: '#0fa',
},
button: {
// width: 192,
// height: 48,
alignSelf: 'center',
},
logoutButton: {
color: '#fff',
},
mensagens: {},
card: { borderColor: '#f00', borderWidth: 2 },
separador: {
height: 1,
width: '100%',
backgroundColor: '#aaa',
marginBottom: 4,
},
splashLogo: {
padding: 8,
width: 32,
height: 32,
},
imageMessage: {
margin: 16,
width: '100%',
height: 200,
},
textInputStyle: { flexGrow: 1 },
containerTextInput: {
flexDirection: 'row',
alignItems: 'center',
},
buttonTextInput: {},
});