–––
Introdução ao React Native
Antonio Lopes Jr React Native, 1ª Versão Sem Revisão
1
Sumário 1 Introdução .................................................................................................................3
1.1 Pré-requisitos ....................................................................................................4
2 Ambiente de Desenvolvimento..................................................................................5
2.1 Windows ...........................................................................................................5
2.1.1 Chocolatey ................................................................................................5
2.1.2 JDK ...........................................................................................................6
2.1.3 Android Studio ..........................................................................................6
2.1.4 Criando Emulador no Android ..................................................................9
2.1.5 Python ....................................................................................................13
2.1.6 Node JS e Node Package Manager .......................................................13
2.1.7 React Native CLI ....................................................................................14
2.1.8 Visual Studio Code .................................................................................14
2.1.9 Variáveis de Ambiente ............................................................................16
2.1.10 Testando o Ambiente de Desenvolvimento ............................................19
2.2 Linux ...............................................................................................................21
2.3 Mac OS ...........................................................................................................21
3 Todo List Manager ..................................................................................................22
3.1 Iniciando o Projeto ToDoManager ..................................................................24
3.2 Styles ..............................................................................................................28
3.3 Layout e Flexbox .............................................................................................30
3.4 Tela de Login ..................................................................................................34
3.5 Props e State ..................................................................................................39
3.6 Tela de Registro ..............................................................................................43
3.7 Navegação entre Telas ...................................................................................48
2
3.8 Integração com o Firebase .............................................................................56
3.9 Registrando Usuário .......................................................................................57
3.10 Autenticando Usuário ......................................................................................63
3.11 Tela de Listagem.............................................................................................68
3.12 Verificando o Estado do Usuário .....................................................................77
3.13 Tela de Tarefa .................................................................................................83
3.14 Componente de Listagem ...............................................................................94
3.15 Ajustando a Listagem Done .......................................................................... 103
3.16 Atualizando uma Tarefa ................................................................................ 105
4 Referências Bibliográficas ..................................................................................... 115
3
1 INTRODUÇÃO
Quando mencionamos desenvolvimento de aplicações para dispositivos
móveis sabemos do grande desafio de disponibilizar essas aplicações nas plataformas
Android e iOS. Existem várias abordagens disponíveis para utilizarmos no
desenvolvimento de aplicativos para dispositivos móveis tais como a abordagem de
construção de aplicativos nativos, construção de aplicativos webs ou aplicativos híbridos
e construção de aplicativos multiplataforma. Cada abordagem tem suas vantagens e
desvantagens.
Se utilizarmos uma abordagem de construção de aplicativos nativos,
utilizando as linguagens Java ou Kotlin para Android e Objective-C ou Swift para iOS,
o maior desafio fica por conta do time de desenvolvedores. Essa abordagem requer um
time especialista para construção da nossa aplicação para Android e outro time
especialista responsável pela construção de nosso aplicativo para iOS o que acaba
elevando o custo de desenvolvimento, mas provendo uma experiência mais consistente
ao usuário.
Se utilizarmos uma abordagem de construção de aplicativo web, onde
utilizamos uma linguagem padrão web para geração de conteúdo, não necessitamos de
especialistas em construção de aplicativos para dispositivos móveis, mas de
conhecimento básico em desenvolvimento de cada plataforma. A construção de nossa
aplicação é única e reaproveitamos o código fonte construído para ambas as plataformas,
pois o código fonte roda em um browser. O principal desafio dessa abordagem é
proporcionar uma experiência agradável ao usuário, mas por outro lado, diminuímos o
custo de um time com especialistas em cada plataforma.
A abordagem de construção de aplicativos multiplataforma, que é a
abordagem que vamos utilizar, tem como principio codificar uma única vez utilizando
uma única linguagem e gerar aplicações nativas para Android e iOS. Essa abordagem
é uma combinação das duas abordagens mencionadas anteriormente e também
apresenta desafios que vamos entender melhor durante a leitura da apostila.
4
1.1 PRÉ-REQUISITOS
Embora não seja imprescindível, conhecimento em Javascript é bem-vindo.
Mas os conceitos mais importantes para aproveitarmos bem o conteúdo que iremos
estudar são:
1. Conhecimento em linguagem orientada a objeto;
2. Conhecimento básico em desenvolvimento para Android com Java ou
Kotlin;
3. Conhecimento básico em desenvolvimento para iOS com Objective-C ou
Swift.
Além dos conceitos das linguagens, nós também precisamos nos certificar de
que temos instalados o Java Development Kit (JDK), Android Studio e XCode (no
caso do Mac OS). Essas ferramentas nos ajudarão a compilar nossas aplicações para
as plataformas Android e iOS. Mas faremos isso no próximo capítulo.
5
2 AMBIENTE DE DESENVOLVIMENTO
O React Native é um framework cujo o ambiente de desenvolvimento é
multiplataforma, ou seja, conseguimos instalar o React Native no sistema operacional
Windows, Linux ou Mac OS. Ao final desse capítulo devemos estar aptos a preparar o
ambiente de desenvolvimento do React Native nas plataformas Windows, Linux e Mac
OS.
2.1 WINDOWS
No Windows precisamos instalar o Chocolatey, Java Development Kit, Android Studio, Python, Node JS, Node Package Management (NPM), React Native Command Line Interface (CLI) e Visual Studio Code. Vamos a instalação de cada
uma dessas ferramentas.
2.1.1 Chocolatey
O Chocolatey é um software que ajuda a automatizar processos. Esses
processos são escritos através de scripts. Não iremos construir scripts, mas vamos
executar scripts prontos para instalar os softwares que usaremos em nosso ambiente de
desenvolvimento.
1. Vamos executar o PowerShell em modo administrador;
2. Temos que alterar as políticas de execução de scripts do PowerShell. Vamos executar o comando Set-ExecutionPolicy Bypass -Scope
Process -Force. O PowerShell nos apresentará uma mensagem de
confirmação. Vamos digitar A e pressionar a tecla enter;
Figura 2.1 - Alteração da Política de Execução
6
3. Agora, vamos executar o comando iex ((New-Object
System.Net.WebClient).DownloadString(‘https://chocolat
ey.org/install.ps1’)) para instalar o Chocolatey.
Pronto! Agora a instalação de algumas ferramentas ficará bem mais fácil.
2.1.2 JDK
Como iremos utilizar o ambiente de desenvolvimento para Android, também
precisaremos do JDK. Vamos instalar o JDK pelo Chocolatey.
1. Vamos executar o PowerShell em modo administrador;
2. Agora basta executar o comando choco install -y jdk8.
Fácil assim! Instalando o JDK pelo Chocolatey fica fácil. E ainda, o script de
instalação executado através do Chocolatey configura a variável de ambiente
JAVA_HOME. Menos um item a se preocupar.
2.1.3 Android Studio
Vamos abordar duas formas de instalar o Android Studio: através do
Chocolatey e a forma manual.
Instalar o Android Studio utilizando o Chocolatey é simples.
1. Vamos executar o PowerShell em modo administrador;
2. Vamos executar o comando choco install -y androidstudio.
Assim como ocorre durante a instalação do JDK, o script de instalação do
Android Studio executa alguns passos adicionais, isto é, antes de instalar o Android Studio o script instala o SDK do Android Studio, configura a variável de ambiente
ANDROID_HOME indicando o diretório que o SDK foi instalado e adiciona na variável
de ambiente PATH dois diretórios: ANDROID_HOME\tools e
ANDROID_HOME\plataform-tools.
Instalar o Android Studio manualmente requer executar, também
manualmente, alguns passos adicionais, ou seja, instalar o SDK e configurar as variáveis
de ambiente. Reservaremos um subcapítulo específico para configurarmos as variáveis
de ambiente. Vamos focar na instalação do Android Studio e SDK.
7
1. Vamos abrir um navegador e digitar o endereço
https://developer.android.com/studio/;
2. Vamos clicar no botão “Download Android Studio”;
Figura 2.2 - Downloading Android Studio
3. Na sequência, uma janela será apresentada. Vamos marcar a opção “I have read and agree with the above terms and conditions” e clicar no
botão “Download Android Studio for Windows”;
4. Salvaremos o arquivo instalador do Android Studio na pasta downloads;
5. Vamos aguardar a finalização do download, localizar o arquivo instalador
do Android Studio na pasta de downloads e clicar duas vezes sobre o
mesmo;
6. Vamos clicar no botão “Next”, “Next” outra vez, “Next” mais uma vez e
vamos clicar no botão “Install”;
7. Ao finalizar a instalação, vamos marcar a opção “Start Android Studio”
e clicar no botão “Finish”;
8
Figura 2.3 - Executando o Android Studio.
8. A primeira vez que executarmos o Android Studio uma janela com o título
“Complete Installation” será apresentada. Vamos marcar a opção “Do not import settings” e clicar no botão “Ok”;
Figura 2.4 - Completando a instalação do Android Studio.
9. Na próxima janela, com o subtítulo “Welcome Android Studio” vamos
clicar no botão “Next”;
10. Desta vez, na janela com o subtítulo “Install Type” vamos selecionar a
opção “Stardard” e clicar no botão “Next”;
11. Vamos selecionar o tema e clicar no botão “Next” novamente. Eu gosto
do tema dark, mas podemos escolher qualquer tema;
12. Na janela com o subtítulo “Verify Settings” vamos clicar no botão
“Finish”. Esse passo vai demorar um pouco para finalizar;
9
13. Se tudo ocorrer como esperado, no final da instalação, uma janela com a
mensagem “Deseja permitir que este aplicativo faça alterações no seu dispositivo?” será apresentada. Vamos clicar no botão “Sim”;
14. Por último, vamos clicar no botão “Finish”.
Pronto! Android Studio e SDK instalados manualmente.
2.1.4 Criando Emulador no Android
Durante o desenvolvimento de nosso aplicativo temos que fazer o teste.
Podemos testar em um dispositivo físico com Android ou criar um emulador. Testar
nosso aplicativo no emulador é um cenário que nos atente muito bem. Então, vamos aos
passos para criar um emulador.
1. Vamos abrir o Android Studio; 2. Na janela com o título “Welcome to Android Studio” vamos selecionar a
opção “Start a new Android Studio project”;
Figura 2.5 - Criando projeto no Android Studio.
3. Nas próximas quatro telas vamos clicar, respectivamente, nos botões
“Next”, “Next”, “Next” novamente e, por último, vamos clicar no botão
“Finish”. Esse passo vai demorar um pouco, então, vamos aguardar até
que o Android Studio conclua;
4. Em seguida, vamos clicar no menu “Tools => AVD Manager” ou clicar no
ícone do AVD Manager localizado no canto superior direito do Android
10
Studio. Observe o destaque em vermelho da Figura 2.6 - Ícone do AVD
Manager.;
Figura 2.6 - Ícone do AVD Manager.
5. Uma janela com o título “Android Virtual Device Manager” será
apresentada. Vamos clicar no botão “Create Virtual Device”;
Figura 2.7 - Criando Virtual Device
6. Na lista Category localizada ao lado esquerdo, vamos selecionar Phone e na lista central, vamos selecionar Nexus 5 e clicar no botão “Next”;
11
Figura 2.8 - Selecionando o tipo do emulador
7. Na próxima janela, vamos escolher a versão do Android que iremos
configurar no emulador. Vamos selecionar “Nougat API Level 24” e clicar
no botão “Next”. Observe que existe a palavra download a frente do nome
Nougat. Isso quer dizer que vamos realizar o download da versão do
Android e que vai demorar um pouco;
8. Ao finalizar, vamos clicar no botão “Finish”; 9. Vamos selecionar “Nougat API Level 24” e clicar no botão “Next”;
12
Figura 2.9 - Finalizando a configuração do emulador
10. E por último, vamos clicar no botão “Finish”.
Finalizada a configuração do nosso emulador. Se tudo ocorrer como esperado,
veremos o nosso emulador criado na lista “Your Virtual Devices”. Observe a Figura
2.10 - Your Virtual Devices.
No subcapítulo Testando o Ambiente de Desenvolvimento vamos verificar se
o nosso emulador está funcionando corretamente.
13
Figura 2.10 - Your Virtual Devices
2.1.5 Python
Python é uma linguagem de programação que o React Native utiliza para
transformar os códigos JavaScript Extension (JSX) em JavaScript. Não iremos utilizar
diretamente o Python, mas não devemos nos preocupar com isso, pois o React Native
cuidará dessa conversão para nós. Então, vamos a instalação.
1. Vamos executar o PowerShell em modo administrador;
2. Em seguida, vamos executar o comando choco install -y python.
Pronto! Agora iremos instalar o Node JS.
2.1.6 Node JS e Node Package Manager
Assim como o Python, o Node JS também é uma linguagem de programação.
O Node JS é um framework que fornece interfaces (API) entre a linguagem de
programação JavaScript e o sistema operacional. É o Node JS que interpreta os
códigos JavaScripts de nossa aplicação para executá-los. Junto com o instalador do
Node JS existe o Node Package Manager (NPM). O NPM é o gerenciado de bibliotecas
ou pacotes do Node JS. Mas não vamos nos preocupar com o NPM, pois o processo de
instalação do Node JS já instala o NPM.
Vamos aos passos da instalação do Node JS.
14
1. Vamos executar o PowerShell em modo administrador;
2. Em seguida, vamos executar o comando choco install -y nodejs.
Pronto! Node JS e NPM instalados. Para testar se a instalação procedeu com
sucesso, vamos abrir o prompt de comando e digitar o comando node -v. Esse
procedimento deve informar a versão do Node JS instalada. Vamos também digitar o
comando npm -v. Esse procedimento deve informar a versão do NPM instalada.
2.1.7 React Native CLI
Falta pouco para termos o nosso ambiente pronto para rodar em Windows. A
penúltima ferramenta que iremos instalar é o React Native Command Line Interface.
O React Native CLI é uma ferramenta de linha de comando que nos auxiliará na criação
dos projetos React Native e execução dos aplicativos nos emuladores e dispositivos,
entre outros comandos.
1. Vamos executar o PowerShell em modo administrador;
2. Vamos executar o comando npm install –g react-native-cli e
aguardar a finalização da instalação.
Pronto! Vamos agora instalar o Visual Studio Code.
2.1.8 Visual Studio Code
O Visual Studio Code (VS Code) é a ferramenta de interface que utilizaremos
para codificar nosso aplicativo com React Native. Vamos aos passos dessa instalação.
1. Vamos acessar o site https://code.visualstudio.com e clicar na opção de
Download for Windows. Vamos salvar o instalador na pasta download;
2. Vamos localizar o instalador do Visal Studio Code na pasta download e
clicar duas vezes para iniciar a instalação;
3. Na janela que abrirá após o passo descrito no item 2, vamos clicar no botão
“OK”;
15
Figura 2.11 [Inicializando a instalação do Visual Studio Code]
4. Na sequência, vamos clicar no botão “Next”;
5. Na tela seguinte, temos que aceitar os termos de licença, selecionando a
opção “I accept the agreement” e clicar no botão “Next”;
Figura 2.12 [Aceitando os termos da licença]
6. Vamos manter a pasta indicada para instalar o “Visual Studio Code”
clicando no botão “Next” novamente;
7. Vamos clicar no botão “Next” nas duas próximas janelas e, na sequência,
vamos clicar no botão “Install”;
8. Para finalizar a instalação, vamos desmarcar a opção “Launch Visual Studio Code” e clicar no botão “Finish”.
Figura 2.13 [Finalizando a instalação do Visual Studio Code]
16
Pronto! Mais uma ferramenta instalada.
2.1.9 Variáveis de Ambiente
Precisamos configurar duas variáveis de sistema para testarmos o nosso
ambiente de desenvolvimento. As variáveis são JAVA_HOME e Path. Na variável
JAVA_HOME iremos mapear o caminho do Java Development Kit (JDK) para ser
utilizado no processo de compilação do Android. Na variável Path iremos mapear o
caminho do Android Debug Bridge (ADB) para nos ajudar no processo de deploy e
debug do Android. Vamos começar configurando a variável JAVA_HOME.
1. Pressione a tecla Windows e digite Sistema;
2. Na lista, vamos clicar na opção Sistema;
Figura 2.14 [Localizando Configurações de Sistemas]
3. Na sequência, nas configurações do Sistema, no menu a esquerda,
vamos selecionar a opção “Configurações Avançadas do Sistema”;
Figura 2.15 [Configurações Avançadas do Sistema]
17
4. Na janela apresentada a seguir, vamos clicar no botão “Variáveis de Ambiente”;
Figura 2.16 [Variáveis de Ambiente]
5. Agora, vamos clicar no botão “Novo”. No entanto, existem dois botões
“Novo”. Vamos clicar no botão “Novo” localizado na parte inferior;
6. Na janela apresentada na sequência, no campo “Nome da variável”, vamos digitar “JAVA_HOME”. No campo “Valor da variável” vamos
localizar a pasta onde está instalada a JDK e clicar no botão “OK”. No
meu caso esta pasta está localizada em c:\Program
Files\Java\jdk1.8.0_144;
18
Figura 2.17 [Variável JAVA_HOME]
7. O próximo passo é clicar no botão “Editar”. No entanto, existem dois
botões “Editar”. Vamos clicar no botão “Editar” localizado na parte
superior;
8. Na próxima janela apresentada, vamos clicar no botão “Novo” e na
sequência, vamos clicar no botão “Procurar”;
Figura 2.18 [Editando a variável Path]
9. No caminho do diretório, temos que localizar a pasta “platform-tools”
dentro da pasta do SDK do Android. No meu caso esta pasta está
localizada em “c:\Users\Hannibal\AppData\Loca\Android\sdk\platform-tools”;
10. Vamos repetir os passos 7 e 8, mas desta vez para configurar a pasta
“tools”;
11. Por último, vamos fechar as janelas clicando nos botões “OK”.
Pronto! Vamos testar nosso ambiente.
19
2.1.10 Testando o Ambiente de Desenvolvimento
Agora é a hora de cruzar os dedos. Vamos testar nosso ambiente de
desenvolvimento. Vamos dividir o teste do nosso ambiente em três passos: abrir o
emulador; criar um projeto em React Native; compilar e executar o projeto no emulador.
2.1.10.1 Abrir o emulador
Para facilitar o desenvolvimento do nosso aplicativo vamos abri o emulador
do Android através de linha de comando. Esse procedimento é bem simples.
1. Vamos abrir o Power Shell em modo administrador;
2. Em seguida, vamos executar o comando emulator -list-avds;
3. Uma listagem será apresentada. Se seguimos os passos descritos no
subcapítulo Criando Emulador no Android um dos itens listados deve ser
“Nexus_5_API_24”; 4. Por último, vamos executar o comando emulator -avd
Nexus_5_API_24.
Pronto! Agora é só aguardar alguns instantes que veremos nosso emulador
pronto para ser utilizado.
2.1.10.2 Criando um Projeto
Para criar um projeto em React Native iremos utilizar o comando react-
native init, que é um comando reconhecido pelo React Native CLI para criar um
projeto inicial. Vamos ao passo-a-passo.
1. Vamos abrir outra instância do Power Shell em modo administrador;
2. Em seguida, vamos navegar até a nossa pasta de projetos React Native.
No meu caso, minha pasta de projetos fica em
c:\Users\Hannibal\ReactNative;
Figura 2.19 – Criando projeto incial React Native
20
3. Na sequência, vamos executar o comando react-native init
HelloWorld.
Pronto! É só aguardar que um projeto React Native com o nome HelloWorld
será criado.
2.1.10.3 Compilando e executando o projeto
Falta pouco! Vamos aos últimos passos para compilarmos nosso projeto
HelloWorld e testarmos no emulador.
1. Na mesma instância do Power Shell que utilizamos no subcapítulo
anterior vamos entrar na pasta do projeto HelloWorld executando o
comando cd HelloWorld. Temos que certificar que o emulador do
Android esteja aberto;
2. Na sequência, vamos executar o comando react-native run-
android e aguardar até que esse procedimento finalize.
Ufa! Após todos esses passos devemos ter o projeto HelloWorld do Android
sendo executado no emulador. Vamos observar a Figura 2.20 – Projeto HelloWorld
executando no emulador
Figura 2.20 – Projeto HelloWorld executando no emulador
21
2.2 LINUX
No Linux precisamos instalar o Java Development Kit, Android Studio, Node JS, Node Package Management (NPM), React Native Command Line Interface (CLI) e Visual Studio Code. Vamos a instalação de cada uma dessas ferramentas.
2.3 MAC OS
No Mac OS precisamos instalar o Java Development Kit, Android Studio, XCode, Homebrew, Node JS, Node Package Management (NPM), React Native Command Line Interface (CLI) e Visual Studio Code. Vamos a instalação de cada
uma dessas ferramentas.
22
3 TODO LIST MANAGER
Agora que temos nosso ambiente de desenvolvimento pronto, podemos
dedicar um tempo para descrever as principais funcionalidades do nosso aplicativo de
gerenciamento de tarefas.
Teremos quatro telas em nosso aplicativo. Visto pela quantidade de telas
nosso aplicativo parece ser simples, mas iremos explorar vários recursos que o
framework React Native fornece.
A Figura 3.1 – Autenticação com o Firebase
é o protótipo de nossa tela de autenticação com o
Firebase. Embora seja uma tela bem simples, a regra
por trás da tela não é tão simples.
• O usuário digita o e-mail e senha e ao
clicar no botão Sign In, o aplicativo
realiza a autenticação junto ao Firebase;
• Caso o usuário já tenha se cadastrado
anteriormente e digitou os dados
corretamente, a autenticação é realizada
com sucesso e a aplicação redireciona
para a tela de listagem de tarefas;
• Caso o usuário não tenha se cadastrado
anteriormente, a aplicação
disponibilizará a funcionalidade de se
registrar clicando no texto em negrito Register;
• Uma vez que o usuário se autenticou, a aplicação não irá pedir ao mesmo
que se autentique novamente toda vez que inicializar o aplicativo, ou seja,
a aplicação não apresentará a tela de autenticação e redirecionará para a
tela com as tarefas.
Figura 3.1 – Autenticação com o Firebase
23
A Figura 3.2 – Registro de Usuário é o protótipo de nossa tela de registrar
usuário. Essa tela é bem semelhante a tela apresentada
na Figura 3.1 – Autenticação com o Firebase. Vamos
detalhar as características da tela de registro.
• O usuário digita o e-mail e senha e ao clicar no
botão Register User, o aplicativo realiza o registro de
um novo usuário no Firebase;
• Caso o usuário já tenha se cadastrado
anteriormente e digitou os dados corretamente, uma
mensagem de alerta será apresentada informando que
o usuário já está cadastrado;
A Figura 3.3 – Lista de Tarefas é o protótipo
de nossa tela de tarefas. Vamos detalhar as
características da tela de tarefas.
• Na tela de tarefas nós teremos duas abas: To Do e Done;
• Na aba To Do teremos uma listagem com
as tarefas a serem realizadas. Essas
tarefas serão subdivididas em duas
sessões: Hight Priority e Low Priority;
• Na aba Done teremos uma listagem com
as tarefas realizadas;
• Todas as tarefas estarão armazenadas no
Firebase. Então, nossa aplicação será
responsável em buscar essas tarefas no
Firebase e preencher as listagens;
• Cada tarefa da listagem To Do será
composta por um componente que iremos
reutilizá-lo na listagem Done;
Figura 3.2 – Registro de Usuário
Figura 3.3 – Lista de Tarefas
24
• Ao clicar na tarefa, a aplicação deve navegar para a tela com os detalhes
da tarefa;
• O botão representado pelo sinal de mais, ao ser clicado, a aplicação
navegará para a tela de criação de tarefa.
Nosso último protótipo é representado pela Figura 3.4 – Criação e Detalhes
da Tarefa.
• Na tela representada pela Figura 3.4 –
Criação e Detalhes da Tarefa tem como
responsabilidade tanto criar tarefas
quanto atualizar;
• Vamos realizar a validação dos campos
“Title” e “Resume”. Ambos não podem
ser vazios;
• Na criação de uma tarefa, o Switcher “High Priority” deverá ser apresentado
marcado. O Switcher “Is Done?” deverá
ser apresentado desmarcado;
• A aplicação deverá armazenar os dados
da tarefa no Firebase.
3.1 INICIANDO O PROJETO TODOMANAGER
Vamos iniciar e configurar a estrutura básica
do nosso projeto. A configuração básica consiste em organizar o nosso código fonte em
uma estrutura mais organizada e configurar a inicialização do nosso aplicativo para
considerar uma implementação única, ou seja, o mesmo código para Android e iOS.
1. Vamos abrir o Terminal no MacOS ou, se o sistema operacional for
Windows, vamos abrir o PowerShell; 2. Precisamos navegar dentro da pasta onde normalmente criamos nossos
projetos, por exemplo, ../User/Documents/Projects;
Figura 3.4 – Criação e Detalhes da Tarefa
25
3. Agora, vamos digitar a linha de comando react-native init
ToDoManager e pressionar a tecla Enter;
Este procedimento pode levar alguns minutos. Vamos aguardar!
4. Vamos abrir a pasta do nosso projeto no Visual Studio Code. No VS Code
clique no menu “File => Open” e selecione a pasta do projeto
ToDoManager que acabamos de criar;
Figura 3.5 – Abrindo a pasta do projeto no VS Code
5. Com o objetivo de organizar a estrutura do nosso projeto, vamos criar
algumas pastas. Na raiz do projeto, vamos criar a pasta “src”;
6. Dentro da pasta “src”, vamos criar mais cinco pastas: assets,
components, routes, screens e services. Ao final, devemos ter uma
estrutura semelhante a Figura 3.6 – Estrutura do projeto;
Figura 3.6 – Estrutura do projeto
Até agora, temos o nosso projeto criado e estruturado. Vamos codificar um
pouquinho.
7. Vamos mover o arquivo App.js para dentro da pasta “src/screens”;
26
8. Agora, vamos abrir o arquivo index.js e atualizar o import da classe App
de acordo com o código disponível na Tabela 3.1 – Ajustando os imports
do arquivo index.js;
JavaScript import { AppRegistry } from 'react-native'; import App from './src/screens/App'; import { name as appName } from './app.json'; AppRegistry.registerComponent(appName, () => App);
Tabela 3.1 – Ajustando os imports do arquivo index.js
Temos dois pontos importantes no código da Tabela 3.1 – Ajustando os
imports do arquivo index.js. O primeiro é que utilizamos o import para referenciar a
classe App que movemos anteriormente. O segundo é que registramos a classe App no
AppRegistry como sendo a classe inicial da nossa aplicação. A API AppRegistry informa ao React Native qual é o componente inicial de nossa aplicação.
Falta pouco para finalizarmos a configuração inicial de nosso projeto. Vamos
compilar e instalar nosso projeto nos emuladores do Android e iPhone.
9. Vamos abrir o terminal dentro do VS Code. Clique no menu “View => Integrated Terminal”;
Figura 3.7 – Abrindo integrated terminal
Este passo abrirá uma visão do terminal dentro do VS Code. Com isso,
podemos executar os comandos pelo próprio VS Code.
27
Tabela 3.2 – Terminal integrado com o VS Code
10. Vamos executar o comando react-native run-ios e pressionar a
tecla Enter; 11. Ao finalizar o comando executado no item anterior, vamos executar outro
comando, desta vez o comando é react-native run-android. Não
podemos esquecer de abrir o emulador do Android. Podemos revisar esse
procedimento no subcapítulo Abrir o emulador.
Pronto! Neste momento, temos que ter o nosso projeto rodando nos
emuladores do Android e iPhone. Observe as figuras abaixo.
Figura 3.8 – Componente App no iPone
Figura 3.9 – Componente App no Android
28
3.2 STYLES
A definição de estilos em um componente em React Native é bem semelhante
a definição de estilos proposta pelo Cascading Style Sheets (CSS) na web. Todo
Component ou classe que herda Component possui a propriedade style. Os nomes e
valores dos estilos, normalmente correspondem à mesma forma como o CSS funciona
na Web, exceto que os nomes são escritos em minúsculos e sem hífen como, por
exemplo, backgroundColor ao invés de background-color.
Vamos a um exemplo.
1. Vamos abrir o arquivo App.js localizado dentro da pasta src; 2. Vamos adicionar os estilos que iremos utilizar em nosso exemplo abaixo
da classe App;
JavaScript import React, { Component } from 'react'; import { View, Text, StyleSheet } from 'react-native'; export default class App extends Component { ... } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, bigBlue: { color: 'blue', fontSize: 50 }, smallRed: { color: 'red', fontSize: 20 } });
Tabela 3.3 – Definindo Estilos
3. Agora, dentro da classe App, vamos substituir o código do método render pelo disponível na Tabela 3.4 – Utilizando Estilos.
JavaScript import React, { Component } from 'react'; import { View, Text, StyleSheet } from 'react-native'; export default class App extends Component { render() { return (
29
<View style={styles.container}> <Text style={styles.bigBlue}>Big Blue</Text> <Text style={styles.smallRed}>Small Red</Text> </View>); } }
Tabela 3.4 – Utilizando Estilos
Este é um exemplo bem simples. Apenas alteramos a cor e o tamanho da
fonte. O mais importante a observar nesse exemplo é a forma na qual referenciamos o
estilo, ou seja, se observarmos a propriedade style dos dois componentes Text notaremos que estamos referenciando aos estilos bigBlue e smallRed criados através
da constante styles.
Podemos observar o resultado do código disponível na Tabela 3.4 – Utilizando
Estilos nas figuras Figura 3.10 – Estilo aplicado no iPhone e Figura 3.11 – Estilo aplicado
no Android.
Figura 3.10 – Estilo aplicado no iPhone
Figura 3.11 – Estilo aplicado no Android
Nosso arquivo App.js completo está disponível na Tabela 3.5 – Arquivo App.js.
JavaScript import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; export default class App extends Component { render() {
30
return ( <View style={styles.container}> <Text style={styles.bigBlue}>Big Blue</Text> <Text style={styles.smallRed}>Small Red</Text> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, bigBlue: { color: 'blue', fontSize: 50 }, smallRed: { color: 'red', fontSize: 20 } });
Tabela 3.5 – Arquivo App.js
3.3 LAYOUT E FLEXBOX
Antes de iniciarmos de fato o desenvolvimento do nosso projeto
ToDoManager, é extremamente importante entendemos como o posicionamento dos
componentes se comportam no React Native.
React Native fornece um recurso conhecido como Flexbox, ao qual foi
desenvolvido para prover layouts consistentes em tamanhos de telas diferentes. Com o
Flexbox definimos como será o posicionamento dos elementos filhos de um componente.
Podemos definir que os elementos filhos serão posicionados horizontalmente, um a baixo
do outro, que ocuparão todo o espaço disponível ou apenas metade desse espaço,
centralizados verticalmente, entre outras possibilidades.
Basicamente, utilizamos quatro propriedades: flex, flexDirection, alignItems
e justifyContent.
Propriedade Descrição flex A propriedade flex determina qual será o tamanho do componente de forma
dinâmica, de acordo com o tamanho disponível. Por exemplo, se em uma linha possuir três componentes com a propriedade flex igual a 1, 2 e 3, respectivamente, o primeiro componente ocupará 1/6 (16 %) da linha, o segundo 2/6 (33%) da linha e o último componente ocupará 3/6 (50%) da linha.
31
Notamos que o número 6, o divisor dos cálculos, é a soma da propriedade flex dos três componentes da linha.
flexDirection A propriedade flexDirection determina como os componentes filhos serão organizados. flexDirection: ‘row’ indica que os componentes serão organizados em linhas, ou seja, horizontalmente. flexDirection: ‘column’ indica que os componentes serão organizados em colunas, isto é, verticalmente.
justifyContent A propriedade justifyContent determina como os componentes filhos serão alinhados. O alinhamento é em relação a direção definida na propriedade flexDirection, isto é, se a direção for definida horizontalmente – igual a row –, o alinhamento também será na horizontal. flexDirection: ‘row’, justifyContent: ‘center’ indica que os componentes serão centralizados horizontalmente. flexDirection: ‘column’, justifyContent: ‘center’ indica que os componentes serão centralizados verticalmente. Existem ainda outras opções como o flex-start, flex-end, space-around, space-between e space-evenly. justifyContent: ‘space-around’ indica que os componentes serão posicionados com espaços iguais nas extremidades. justifyContent: ‘space-between’ indica que os componentes serão posicionados com espaços iguais entre os elementos. justifyContent: ‘space-evenly’ indica que os componentes serão posicionados com espaços iguais nas extremidades e entre os elementos.
alignItems A propriedade alignItems também determina como os componentes filhos serão alinhados. No entanto o alinhamento é em relação oposta a direção definida na propriedade flexDirection, isto é, se a direção for definida horizontalmente – igual a row –, o alinhamento será na vertical. flexDirection: ‘row’, alignItems: ‘center’ indica que os componentes serão centralizados verticalmente, ou seja, no sentido oposto da direção. flexDirection: ‘column’, alignItems: ‘center’ indica que os componentes serão centralizados horizontalmente, ou seja, no sentido oposto da direção.
Tabela 3.6 – Propriedades Flexbox
Para entendermos melhor, vamos a um exemplo.
1. Com nosso arquivo App.js aberto, vamos alterar nosso método render de
acordo com o disponível na Tabela 3.7 – View com Flexbox.
JavaScript import React, { Component } from 'react'; import { StyleSheet, SafeAreaView, View } from 'react-native'; export default class App extends Component { render() { return ( <SafeAreaView ref='main' style={styles.container}>
32
<View ref='first' style={styles.first}> <View style={styles.subView} /> <View style={styles.subView} /> <View style={styles.subView} /> </View> <View ref='second' style={styles.second}> <View style={styles.subView} /> <View style={styles.subView} /> <View style={styles.subView} /> </View> </SafeAreaView> ); } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column' }, first: { flex: 1, flexDirection: 'row', justifyContent: 'space-between', alignItems: flex-start, margin: 40, borderColor: 'red', borderWidth: 1 }, second: { flex: 2, flexDirection: 'column', justifyContent: 'space-evenly', alignItems: flex-end, margin: 40, borderColor: 'red', borderWidth: 1 }, subView: { height: 50, width: 50, backgroundColor: 'skyblue' }, });
Tabela 3.7 – View com Flexbox
O primeiro componente View referenciado como main cujo a propriedade flex
é igual a 1 indica que o componente ocupará todo o espaço disponível. Como o
componente é a raiz da página, logo, o espaço utilizado será a página toda. A
propriedade flexDirection igual a column indica que os componentes filhos serão
posicionados um em baixo do outro. Esses componentes são as duas Views com bordas
vermelhas, referenciados como first e second.
33
As Views first e second possuem os valores da propriedade flex iguais a 1
e 2, respectivamente. Isso quer dizer que a View main será dividida em três partes. A
primeira parte será ocupada pela View first. Já a View second ocupará os espaços
referentes as partes dois e três.
A View first com a propriedade flexDirection igual a row indica que os
componentes filhos serão posicionados um ao lado do outro, ou seja, serão posicionados
horizontalmente. A propriedade justifyContent igual a space-between indica que os
componentes serão posicionados com espaços iguais entre eles e a propriedade
alignItems igual a flex-end indica que os componentes filhos serão posicionados
verticalmente na parte inferior.
A View second com a propriedade flexDirection igual a column indica que
os componentes filhos serão posicionados um em abaixo do outro. A propriedade
justifyContent igual a space-evenly indica que os espaços das extremidades e os
espaços entre os componentes serão iguais, e a propriedade alignItems igual a flex-start indica que os componentes filhos serão alinhados horizontalmente a esquerda.
Veja o resultado do código disponível na Tabela 3.7 – View com Flexbox nas
imagens Figura 3.12 – iPhone com Flexbox e Figura 3.13 – Android com Flexbox.
34
Figura 3.12 – iPhone com Flexbox
Figura 3.13 – Android com Flexbox
A primeira impressão parece um pouco confusa, mas vamos praticar esses
conceitos durante a construção do nosso projeto. Uma outra dica é jogar o Flexbox Froggy. É um jogo bem legal onde o objetivo é posicionar os sapos em um círculo.
Acessamos esse jogo através do link https://flexboxfroggy.com.
3.4 TELA DE LOGIN
Agora que temos um breve conceito de como o layout funciona em React Native, vamos começar a codificar o nosso projeto.
1. Com o nosso projeto aberto no VS Code, crie um arquivo Login.js dentro
da pasta src/screens/; 2. Com o arquivo Login.js aberto, vamos começar a codificar. Vamos criar a
classe Login e definir alguns estilos;
JavaScript import React, { Component } from 'react'; import { StyleSheet } from 'react-native'; const img = require('../assets/TodoList.png');
35
export default class Login extends Component { ... } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column' }, topView: { justifyContent: 'center', alignItems: 'center', padding: 50 }, img: { width: 200, height: 200 }, bottomView: { flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 }, textConteiner: { flexDirection: 'row', justifyContent: 'center', marginTop: 20 }, textRegister: { fontWeight: 'bold' } });
Tabela 3.8 – Código fonte da classe Login
No trecho de código apresentado na Tabela 3.8 – Código fonte da classe
Login importamos o objeto StyleSheet porque iremos utilizar em nossa classe Login.
Também definimos uma referência para uma imagem e definimos os estilos que iremos
utilizar em nossa classe Login.
3. O próximo passo é definir o conteúdo de nossa classe Login. Vamos
localizar o método render e alterar de acordo com o código apresentado
na Tabela 3.9 – Definindo o conteúdo do Login;
JavaScript ... render() { return ( <SafeAreaView style={{ flex: 1 }}> <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}>
36
<Image style={styles.img} source={img} /> </View> <View style={styles.bottomView}> <TextInput style={styles.input} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} /> <Button title='Sign In' /> <View style={styles.textConteiner}> <Text>Not a member? Let's </Text> <Text style={styles.textRegister}> Register </Text> </View> </View> </KeyboardAvoidingView> </SafeAreaView> ); } ...
Tabela 3.9 – Definindo o conteúdo do Login
O conteúdo de nosso componente Login possui a seguinte hierarquia:
• KeyboardAvoidingView principal que ocupa a tela inteira e seus
componentes filhos são posicionados um abaixo do outro:
o View que é posicionada na metade superior da tela:
§ Image que é posicionada ao centro, cujo o tamanho é
200x200;
o View que é posicionada na metade inferior da tela e seus
componentes filhos são posicionados um abaixo do outro:
§ TextInput para email; § TextInput para password; § Button para realizar a autenticação; § View com seus componentes filhos:
• Text para o texto “Not a member? Let’s”;
• Text para o texto “Register”.
Se observarmos o código da Tabela 3.9 – Definindo o conteúdo do Login não
mencionamos o componente SafeAreaView. O propósito desse componente é ajustar o
preenchimento das bordas que não são cobertas por barras de navegação, guias, etc.
37
Em outras palavras, esse é um recurso que se aplica ao iPhone X cujo a borda superior
não é preenchida pela barra de navegação.
4. Na sequência, temos que importar os objetos que utilizamos;
JavaScript import React, { Component } from 'react'; import { SafeAreaView, KeyboardAvoidingView, View, Image, TextInput, Button, Text, StyleSheet } from 'react-native'; ...
Tabela 3.10 – Ajustando os imports do Login
5. O último passo é abrir o arquivo Index.js e atualizar o código de acordo
com o disponível na Tabela 3.11 – Código do arquivo index.js atualizado.
JavaScript import { AppRegistry } from 'react-native'; import Login from './src/screens/Login';
AppRegistry.registerComponent('ToDoManager', () => Login);
Tabela 3.11 – Código do arquivo index.js atualizado
Podemos observar o resultado do nosso código através das imagens Figura
3.14 – Tela de login no iPhone e Figura 3.15 – Tela de login no Android.
38
Figura 3.14 – Tela de login no iPhone
Figura 3.15 – Tela de login no Android
O código completo de nossa classe Login está disponível na Tabela 3.12 –
Classe Login.
JavaScript import React, { Component } from 'react'; import { SafeAreaView, KeyboardAvoidingView, StyleSheet, View, Image, TextInput, Button, Text, SafeAreaView, Alert } from 'react-native'; const img = require('../assets/TodoList.png'); export default class Login extends Component { render() { return ( <SafeAreaView style={{ flex: 1 }}> <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> </View> <View style={styles.bottomView}> <TextInput style={styles.input} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} /> <Button title='Sign In'/> <View style={styles.textConteiner}> <Text>Not a member? Let's </Text>
39
<Text style={styles.textRegister}> Register </Text> </View> </View> </KeyboardAvoidingView> </SafeAreaView> ); } }
const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column' }, topView: { justifyContent: 'center', alignItems: 'center', padding: 50 }, img: { width: 200, height: 200 }, bottomView: { flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 }, textConteiner: { flexDirection: 'row', justifyContent: 'center', marginTop: 20 }, textRegister: { fontWeight: 'bold' } });
Tabela 3.12 – Classe Login
O código completo da classe App Tabela 3.13 – Classe index com Login.
JavaScript import { AppRegistry } from 'react-native'; import Login from './src/screens/Login'; import { name as appName } from './app.json'; AppRegistry.registerComponent(appName, () => Login);
Tabela 3.13 – Classe index com Login
3.5 PROPS E STATE
Antes de implementarmos a autenticação de fato, precisamos entender dois
conceitos do React Native: Props e State.
40
Existem dois tipos de estrutura de dados em um componente. Um desses
tipos é o Props, cujo se caracteriza por ser imutável e é utilizado para o componente pai
passar valores como parâmetros. Outro tipo de estrutura de dados é o State que, ao
contrário do Props, seus valores podem ser alterados, ou seja, é mutável.
Melhor entendermos o Props com um exemplo.
1. Vamos abrir o arquivo index.js e alterar o código de acordo com código
apresentado na Tabela 3.14 – Props como parâmetro.
JavaScript import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import Login from './src/screens/Login'; import { name as appName } from './app.json'; class Index extends Component { render() { return (<Login email='[email protected]' />); } } AppRegistry.registerComponent(appName, () => Index);
Tabela 3.14 – Props como parâmetro
Observe que no componente <Login/> estamos acrescentando uma
propriedade chamada email e atribuindo o valor ‘[email protected]’. Em outras
palavras, o componente pai, que neste caso é classe App que criamos, passa para o
componente <Login/> um parâmetro, cujo não será alterado durante todo o ciclo de vida
do componente <Login/>.
Para recuperarmos esse valor no componente <Login/> temos que codificar
um pouquinho.
2. Vamos abrir o arquivo Login.js, localizar o método render e adicionar a
propriedade value={this.props.email} no componente <TextInput/> do
email. Observe o código fonte disponível na Tabela 3.15 – Recuperando
Props como parâmetro.
JavaScript render() { return ( <SafeAreaView style={{ flex: 1 }}> <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}>
41
<Image style={styles.img} source={img} /> </View> <View style={styles.bottomView}> <TextInput style={styles.input} value={this.props.email} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' /> ...
Tabela 3.15 – Recuperando Props como parâmetro
Como vimos no início do capítulo, o outro tipo de estrutura de dados é o State. No entanto, apenas vimos, não codificamos nada ainda. Então, vamos fazer algumas
alterações na classe Login.
3. A primeira alteração é a declaração dos nossos states. Vamos observar o
trecho de código da Tabela 3.16 – Declarando states na classe Login;
JavaScript ... export default class Login extends Component { state = { email: this.props.email, password: '' }; ...
Tabela 3.16 – Declarando states na classe Login
4. Na sequência, vamos localizar o método render e implementar o evento
onChangeText nos componentes <TextInput/>. Também vamos
aproveitar para implementar o evento onPress do componente <Button/>.
Assim, conseguiremos testar os nossos states. Observe o código
apresentado na Tabela 3.17 – Alterando os States na classe Login;
JavaScript ... <View style={styles.bottomView}> <TextInput style={styles.input} value={this.state.email} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' onChangeText={(text) => this.setState({ email: text })} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={(password) => this.setState({ password })} /> <Button title='Sign In' onPress={() => Alert.alert(`Email: ${this.state.email} \nPassword: ${this.state.password}`)} /> ...
42
Tabela 3.17 – Alterando os States na classe Login
Fizemos três alterações no nosso código e a primeira foi utilizar o evento
onChangeText do componente <InputText/> para capturar o e-mail digitado pelo
usuário e atribuir no state email. A segunda alteração também foi utilizar o evento
onChangeText do componente <InputText/>, mas dessa vez para capturar a senha
digitada pelo usuário e atribuir no state password. A última alteração foi utilizar o evento
onPress do componente <Button/> para apresentar uma alerta com o e-mail e senha
digitados.
Podemos ver nosso código completo da classe Login na Tabela 3.18 –
Refactor do Login com States.
JavaScript import React, { Component } from 'react'; import { StyleSheet, SafeAreaView, KeyboardAvoidingView, View, Image, TextInput, Button, Text, Alert } from 'react-native'; const img = require('../assets/TodoList.png'); export default class Login extends Component { state = { email: this.props.email, password: '' }; render() { return ( <SafeAreaView style={{ flex: 1 }}> <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> </View> <View style={styles.bottomView}> <TextInput style={styles.input} value={this.state.email} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' onChangeText={(text) => this.setState({ email: text })} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={(text) => this.setState({ password: text })} /> <Button title='Sign In' onPress={() => Alert.alert(`Email: ${this.state.email} \nPassword: ${this.state.password}`)} /> <View style={styles.textConteiner}> <Text>Not a member? Let's </Text>
43
<Text style={styles.textRegister}> Register </Text> </View> </View> </KeyboardAvoidingView> </SafeAreaView> ); } } const styles = StyleSheet.create({ container: { flex: 1 }, topView: { justifyContent: 'center', alignItems: 'center', padding: 50 }, img: { width: 200, height: 200 }, bottomView: { flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 }, textConteiner: { flexDirection: 'row', justifyContent: 'center', marginTop: 20 }, textRegister: { fontWeight: 'bold' } });
Tabela 3.18 – Refactor do Login com States
Para testarmos nosso código vamos digitar um conteúdo qualquer nos inputs
do Email e Password e pressionar o botão Sign In. Se tudo funcionar como esperado,
uma alerta será apresentada com o conteúdo digitado.
3.6 TELA DE REGISTRO
Antes de estudarmos a navegação entre telas e integrarmos nosso projeto
com o Firebase vamos criar nossa página de registro de usuário.
44
1. Com o nosso projeto aberto no VS Code, crie um arquivo Register.js
dentro da pasta src/screens/; 2. Com o arquivo Register.js aberto, vamos criar a classe Register e o
estilos que iremos utilizar. Vamos observar o código disponível na Tabela
3.19 – Código fonte da classe Register;
JavaScript import React, { Component } from 'react'; import { StyleSheet } from 'react-native'; const img = require('../assets/TodoList.png'); export default class Register extends Component { ... } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', justifyContent: 'center' }, topView: { flex: 0.20, flexDirection: 'row', alignItems: 'center', padding: 25 }, img: { width: 50, height: 50 }, title: { fontSize: 20, fontWeight: 'bold', marginLeft: 20 }, bottomView: { flex: 1, flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 } });
Tabela 3.19 – Código fonte da classe Register
3. Temos que definir a estrutura dos states para receber os dados
preenchidos no e-mail e password;
JavaScript state = { email: '',
45
password: '' }
Tabela 3.20 – Declarando os states da classe Register
4. Vamos implementar o conteúdo de nossa classe Register. Vamos
localizar o método render e trabalhar no visual;
JavaScript render() { return ( <SafeAreaView style={{ flex: 1 }}> <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> <Text style={styles.title}>Registering new user</Text> </View> <View style={styles.bottomView}> <TextInput style={styles.input} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' onChangeText={email => this.setState({ email })} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={password => this.setState({ password })} /> <Button title='Register User' onPress={() => Alert.alert(`Email: ${this.state.email}\n Password: ${this.state.password}`)} /> </View> </KeyboardAvoidingView> </SafeAreaView> ); }
Tabela 3.21 – Definindo o método render da classe Register
Antes de seguirmos com a codificação de nossa classe Register, e como
forma de exercício para fixarmos o entendimento do flexbox, vamos entender como ficou
a hierarquia de nossa classe:
• KeyboardAvoidingView principal que ocupa a tela inteira e seus
componentes filhos são posicionados um abaixo do outro:
o View que é posicionada no ¼ superior da tela e seus elementos
filhos são posicionados um ao lado do outro:
§ Image que é posicionada ao esquerdo, cujo o tamanho é
50x50;
§ Text que é posicionado ao lado direito da Image.
46
o View que é posicionada no ¾ inferior da tela e seus componentes
filhos são posicionados um abaixo do outro:
§ TextInput para email; § TextInput para password; § Button para realizar o registro.
Se observarmos o código da Tabela 3.21 – Definindo o método render da
classe Register novamente iremos encontrar o componente SafeAreaView, mas já
sabemos o motivo, ou seja, O propósito desse componente é ajustar o preenchimento
das bordas que não são cobertas por barras de navegação, guias, etc.
5. Para finalizarmos a codificação de nossa classe Register, vamos importar
os componentes que utilizamos;
JavaScript import React, { Component } from 'react'; import { SafeAreaView, KeyboardAvoidingView, View, Image, Text, TextInput, Button, StyleSheet, Alert } from 'react-native'; ...
Tabela 3.22 – Ajustando os imports da classe Register
6. E para testarmos nossa classe Register, vamos abrir o arquivo index.js e
atualizar o código de acordo com o disponível na Tabela 3.23 – Código da
classe index atualizado.
JavaScript import { AppRegistry } from 'react-native'; import Register from './src/screens/Register'; import { name as appName } from './app.json'; AppRegistry.registerComponent(appName, () => Register);
Tabela 3.23 – Código da classe index atualizado
Podemos observar o resultado do nosso código através das imagens Figura
3.16 – Tela de registro no iPhone e Figura 3.17 – Tela de registro no Android.
47
Figura 3.16 – Tela de registro no iPhone
Figura 3.17 – Tela de registro no Android
Nossa classe Register completa ficou igual ao código apresentado na Tabela
3.24 – Classe Register.
JavaScript import React, { Component } from 'react'; import { SafeAreaView, KeyboardAvoidingView, View, Image, Text, TextInput, Button, StyleSheet, Alert } from 'react-native'; const img = require('../assets/TodoList.png'); export default class Register extends Component { state = { email: '', password: '' } render() { return ( <SafeAreaView style={{ flex: 1 }}> <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> <Text style={styles.title}>Registering new user</Text> </View> <View style={styles.bottomView}> <TextInput style={styles.input} placeholder='Email' keyboardType={'email-address'}
48
autoCapitalize='none' onChangeText={email => { this.setState({ email }) }} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={password => this.setState({ password })} /> <Button title='Register User' onPress={() => { Alert.alert(`Email: ${this.state.email}\n Password: ${this.state.password}`) }} /> </View> </KeyboardAvoidingView> </SafeAreaView> ); } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', justifyContent: 'center' }, topView: { flex: 0.20, flexDirection: 'row', alignItems: 'center', padding: 25 }, img: { width: 50, height: 50 }, title: { fontSize: 20, fontWeight: 'bold', marginLeft: 20 }, bottomView: { flex: 1, flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 } });
Tabela 3.24 – Classe Register
3.7 NAVEGAÇÃO ENTRE TELAS
Agora que temos duas telas precisamos realizar a navegação da tela de Login para a tela de Register. Para isso, vamos estudar uma biblioteca chamada React Navigation. Começaremos instalando a biblioteca.
49
1. No terminal integrado do VS Code, vamos digitar o comando npm
install –-save react-navigation.
A instalação pode levar alguns minutos. Então, vamos aguardar a instalação
concluir para codificarmos um pouco.
2. Vamos aproveitar a oportunidade e criar o arquivo Screens.js dentro da
pasta src/screens. Vamos importar as classes Login e Register e, na
sequência, exportar suas referências;
JavaScript import Login from './Login'; import Register from './Register'; export { Login, Register };
Tabela 3.25 - Centralizando os exports das páginas
No trecho de código apresentado na Tabela 3.25 - Centralizando os exports
das páginas importamos as páginas Login e Register e criamos um esquema para
centralizar os imports e exports de nossas páginas. O objetivo é simplificar os imports
toda vez que precisarmos usar uma página. Veremos com ficará mais adiante.
3. Com o nosso projeto aberto no VS Code, crie um arquivo Routes.js dentro
da pasta src/services/; 4. Com o arquivo Routes.js aberto, vamos configurar nossas rotas de
navegação. Vamos observar o código disponível na Tabela 3.26 – Código
fonte das rotas de navegação;
JavaScript import { createStackNavigator } from 'react-navigation'; import { Login, Register } from '../screens/Screens'; export default Routes = createStackNavigator( { pageLogin: { screen: Login }, pageRegister: { screen: Register } }, { headerMode: 'screen' } );
Tabela 3.26 – Código fonte das rotas de navegação
50
O código acima é bem simples. Importamos o método createStackNavigator e as páginas Login e Register. Observe que agora nossas páginas estão definidas
em ../screens/Screens e especificamos somente as páginas que iremos utilizar dentro
das chaves. Outro ponto importante a ser observado é o método createStackNavigator, responsável por configurar as rotas de navegação. Então, criamos uma constante
chamada Routes, que é uma instância do componente StackNavigator, e, por enquanto,
definimos duas rotas: Login e Register. Também definimos a propriedade headerMode como screen, ou seja, cada tela fica responsável em configurar a exibição da barra de
título.
5. Na sequência, vamos abrir o arquivo index.js e atualizar o código de
acordo com o disponível na Tabela 3.27 – Arquivo index.js atualizado com
rotas;
JavaScript import React from 'react'; import { AppRegistry, SafeAreaView } from 'react-native'; import Routes from './src/routes/Routes'; import { name as appName } from './app.json'; const wrappedRoutes = () => { return ( <SafeAreaView style={{ flex: 1 }}> <Routes /> </SafeAreaView> ); }; AppRegistry.registerComponent(appName, () => wrappedRoutes);
Tabela 3.27 – Arquivo index.js atualizado com rotas
Note que agora a responsabilidade em realizar a navegação fica por conta da
constante Routes. Outro ajuste que fizemos foi definir o objeto Routes como filho do
componente SafeAreaView. A partir de agora, qualquer página que navegarmos será
filha do objeto SafeAreaView. Com isso, evitaremos de definir o objeto SafeAreaView
como pai de todas as páginas que criarmos.
Neste ponto, se executarmos nosso aplicativo em Android ou iOS a tela de
Login deve ser apresentada. Falta pouco para concluirmos nossa navegação. Vamos
realizar alterações em mais duas classes.
51
6. Vamos abrir a classe Login e acima do construtor vamos configurar a
propriedade navigationOptions;
JavaScript ... export default class Login extends Component { static navigationOptions = { header: null }; ...
Tabela 3.28 – Configuração da propriedade navigationOptions da classe Login
Neste caso, excluímos a cabeçalho de nossa classe Login, ou seja, excluímos
o título e barra de navegação de nossa classe Login.
7. Ainda em nossa classe Login, vamos alterar o componente
<Text>Register</Text> localizado no método render. Vamos codificar o
evento onPress e vamos aproveitar e remover o componente
<SafeAreView> do método render;
JavaScript ... render() { return ( <KeyboardAvoidingView style={styles.container} behavior='padding'> ... <Text style={styles.textRegister} onPress={() => { const { navigate } = this.props.navigation; navigate('pageRegister'); }} > Register </Text> ... </KeyboardAvoidingView> ); } ...
Tabela 3.29 – Navegando para a classe Register
No código da Tabela 3.29 – Navegando para a classe Register recuperamos
o objeto navigate das props utilizando um recurso do Javascript chamado destructing object, e navegamos para a página Register ao clicar sobre o texto Register. É
importante entender que nosso esquema de navegação definido no arquivo Routes.js passa como parâmetro o objeto navigation para todas as páginas. É por isso que
conseguimos recuperar o objeto navigation na página Login.
52
Para finalizarmos a navegação, vamos realizar um ajuste na classe Register.
8. Vamos abrir a classe Register e acima do state, vamos configurar a
propriedade navigationOptions e remover o componente
<SafeAreView> do método render.
JavaScript ... export default class Register extends Component { static navigationOptions = { title: 'Register' }; ...
Tabela 3.30 – Configuração da propriedade navigationOptions da classe Register
Pronto! Se executarmos nossa aplicação, a classe Login será apresentada e
ao clicarmos no texto Register a aplicação navegará para a classe Register, mas desta
vez, com um detalhe a mais: título igual a Register e a seta de voltar para a tela Login.
Alteramos quatro classes para configurarmos a navegação inicial de nossa
aplicação e criamos um arquivo para nos ajudar nos imports das páginas. Vamos
observar o resultado dessas classes nas Tabela 3.31 - Esquema de imports e exports
das páginas, Tabela 3.32 – Código das rotas de navegação, Tabela 3.33 – Ajustando o
arquivo index.js, Tabela 3.34 – Código da classe Login navegando para a classe Register
e Tabela 3.35 – Código da classe Register com opções de navegação.
JavaScript import Login from './Login'; import Register from './Register'; export { Login, Register }
Tabela 3.31 - Esquema de imports e exports das páginas
JavaScript import { createStackNavigator } from 'react-navigation'; import { Login, Register } from '../screens/Screens'; export default Routes = createStackNavigator( { pageLogin: { screen: Login }, pageRegister: { screen: Register } }, { headerMode: 'screen' }
53
); Tabela 3.32 – Código das rotas de navegação
JavaScript import React from 'react'; import { AppRegistry, SafeAreaView } from 'react-native'; import Routes from './src/routes/Routes'; import { name as appName } from './app.json'; const wrappedRoutes = () => { return ( <SafeAreaView style={{ flex: 1 }}> <Routes /> </SafeAreaView> ); }; AppRegistry.registerComponent(appName, () => wrappedRoutes);
Tabela 3.33 – Ajustando o arquivo index.js
JavaScript import React, { Component } from 'react'; import { StyleSheet, KeyboardAvoidingView, View, Image, TextInput, Button, Text, Alert } from 'react-native'; const img = require('../assets/TodoList.png'); export default class Login extends Component { static navigationOptions = { header: null }; state = { email: this.props.email, password: '' }; render() { return ( <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> </View> <View style={styles.bottomView}> <TextInput style={styles.input} value={this.state.email} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' onChangeText={(text) => this.setState({ email: text })} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={(text) => this.setState({ password: text })} /> <Button title='Sign In' onPress={() => Alert.alert(`Email: ${this.state.email} \nPassword: ${this.state.password}`)} /> <View style={styles.textConteiner}> <Text>Not a member? Let's </Text>
54
<Text style={styles.textRegister} onPress={() => { const { navigate } = this.props.navigation; navigate('pageRegister'); }}> Register </Text> </View> </View> </KeyboardAvoidingView> ); } } const styles = StyleSheet.create({ container: { flex: 1 }, topView: { justifyContent: 'center', alignItems: 'center', padding: 50 }, img: { width: 200, height: 200 }, bottomView: { flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 }, textConteiner: { flexDirection: 'row', justifyContent: 'center', marginTop: 20 }, textRegister: { fontWeight: 'bold' } });
Tabela 3.34 – Código da classe Login navegando para a classe Register
JavaScript import React, { Component } from 'react'; import { KeyboardAvoidingView, View, Image, Text, TextInput, Button, StyleSheet, Alert } from 'react-native'; const img = require('../assets/TodoList.png'); export default class Register extends Component { static navigationOptions = { title: 'Register' }; state = {
55
email: '', password: '' } render() { return ( <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> <Text style={styles.title}>Registering new user</Text> </View> <View style={styles.bottomView}> <TextInput style={styles.input} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' onChangeText={email => { this.setState({ email }) }} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={password => this.setState({ password })} /> <Button title='Register User' onPress={() => { Alert.alert(`Email: ${this.state.email}\n Password: ${this.state.password}`) }} /> </View> </KeyboardAvoidingView> ); } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', justifyContent: 'center' }, topView: { flex: 0.20, flexDirection: 'row', alignItems: 'center', padding: 25 }, img: { width: 50, height: 50 }, title: { fontSize: 20, fontWeight: 'bold', marginLeft: 20 }, bottomView: { flex: 1, flexDirection: 'column',
56
paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 } });
Tabela 3.35 – Código da classe Register com opções de navegação
3.8 INTEGRAÇÃO COM O FIREBASE
Para realizarmos a autenticação do usuário precisamos integrar nossa
aplicação ao Firebase. Então, vamos instalar o Firebase utilizando o npm. A instalação
do Firebase é muito simples. Vamos aos passos.
1. No terminal integrado do VS Code vamos digitar o comando npm
install –-save firebase;
Pronto! Agora é só aguardar a instalação concluir para codificarmos um pouco.
Vamos criar alguns serviços para facilitar o uso da biblioteca do Firebase.
2. Com o nosso projeto aberto no VS Code, vamos criar o arquivo
FirebaseApi.js dentro da pasta src/services/; 3. Com o arquivo FirebaseApi.js aberto, vamos inserir o código disponível
na Tabela 3.36 – Código fonte do arquivo FirebaseApi;
JavaScript import firebase from 'firebase'; const config = { apiKey: "AIzaSyDtVC3VQ1Z8XzQYDkWwnTOC_NFo8ny5c90", authDomain: "todomanager-5444a.firebaseapp.com", databaseURL: "https://todomanager-5444a.firebaseio.com", projectId: "todomanager-5444a", storageBucket: "todomanager-5444a.appspot.com", messagingSenderId: "254572727152" }; export const initializeFirebaseApi = () => firebase.initializeApp(config);
Tabela 3.36 – Código fonte do arquivo FirebaseApi
Vamos entender essa integração com o Firebase. O primeiro passo é dizer
para o React Native que iremos utilizar a biblioteca do Firebase e fazemos isso
utilizando o import. No Firebase existe um o projeto “web” todomanager-5444a criado
57
especificamente para utilizarmos no nosso projeto ToDoManager. Então, precisamos
carregar os dados desse projeto e utilizamos a constante config para armazenar tais
dados e para indicar a biblioteca do Firebase qual projeto carregar. Por último, criamos
um método initializeFirebaseApi para inicializar o serviço do Firebase e permitir acesso
aos serviços de autenticação e armazenagem.
3.9 REGISTRANDO USUÁRIO
Agora que temos nossas telas de Login e Register e a integração com o
Firebase, vamos consumir nosso primeiro serviço para registrar um usuário no Firebase.
1. Com o nosso projeto aberto no VS Code, vamos abrir o arquivo
FirebaseApi.js localizado dentro da pasta src/services/; 2. Com o arquivo FirebaseApi.js aberto, vamos inserir o código disponível
na Tabela 3.37 – Método para registrar usuário no Firebase abaixo do
método initializeFirebaseApi;
JavaScript ... export const createUserOnFirebaseAsync = async (email, password) => { const { user } = await firebase .auth() .createUserWithEmailAndPassword(email, password); return user; } ...
Tabela 3.37 – Método para registrar usuário no Firebase
O método createUserOnFirebaseAsync recebe como parâmetro o email e
password e repassa esses parâmetros invocando o método
createUserWithEmailAndPassword da biblioteca do Firebase. O método nos retorna
uma promessa de que o usuário será criado, mas caso ocorra algum erro, a promessa
repassa esse erro. Então, para saber se o usuário foi criado precisamos implementar
outro método.
3. Vamos precisar abrir outro arquivo. Este arquivo é o Register.js localizado
dentro da pasta src/screens/; 4. Com o arquivo Register.js aberto, vamos codificar um pouquinho.
Começaremos importando o componente Alert da biblioteca ‘react-native’
58
e o método createUserOnFirebaseAsync que vamos utilizar da classe
FirebaseApi;
JavaScript ... import { KeyboardAvoidingView, View, Image, Text, TextInput, Button, Alert, StyleSheet } from 'react-native'; import { createUserOnFirebaseAsync } from '../services/FirebaseApi'; ...
Tabela 3.38 – Importando o método createUserOnFirebase da classe FirebaseApi
5. Ainda na classe Register, vamos codificar o método _createUserAsync
abaixo do método render;
JavaScript ... async _createUserAsync() { try { const user = await createUserOnFirebaseAsync(this.state.email, this.state.password); Alert.alert('User Created!', `User ${user.email} has succesfuly been created!`); } catch (error) { Alert.alert('Create User Failed!', error.message); } } ...
Tabela 3.39 – Método createUser da classe Register
O método _createUserAsync executa o método
createUserOnFirebaseAsync disponível na classe FirebaseApi. O método
createUserOnFirebaseAsync recebe como parâmetro o email e password e nos
retorna uma promessa de que o usuário será criado, mas caso ocorra algum erro, a
promessa repassa esse erro. Por enquanto, em ambos os casos, codificamos uma
mensagem de alerta para ser exibida, ou seja, em caso de sucesso a mensagem “User has succesfuly been created” será exibida, caso contrário, uma mensagem de erro
aparecerá.
6. Também precisamos alterar o evento onPress de nosso componente
<Button/>. Temos que executar o método _createUserAsync quando o
botão Register User for pressionado;
JavaScript ... render() { return ( <KeyboardAvoidingView style={styles.container}
59
behavior='padding'> ... <Button title='Register User' onPress={() => this._createUserAsync()} /> ... </KeyboardAvoidingView> ); } ...
Tabela 3.40 – Evento onPress do botão Register User
Para testarmos o que codificação até o momento, vamos alterar mais uma
classe.
7. Vamos abrir o arquivo index.js localizado na raiz do projeto. Com o arquivo
index.js aberto, temos que importar o método initializeFirebaseApi e
executa-lo;
JavaScript ... import { initializeFirebaseApi } from './src/services/FirebaseApi'; AppRegistry.registerComponent(appName, () => { initializeFirebaseApi(); return wrappedRoutes; }); ...
Tabela 3.41 – Inicializando a biblioteca do Firebase
Neste ponto, ao entrar no aplicativo a biblioteca do Firebase é iniciada e a
tela de Login é apresentada. Para verificarmos se nossa codificação está correta, vamos
navegar para a tela de Register pressionando o texto Register localizado abaixo do
botão Sign In. Na tela de Register, vamos digitar o e-mail e senha e pressionar o botão
Register User. Se tudo estiver correto com os dados que digitamos as imagens Figura
3.18 – Registrando usuário no iPhone e Figura 3.19 – Registrando usuário no Android
serão apresentadas certificando de que o processo de registro de usuário funcionou
corretamente.
Agora, falta apenas um pequeno detalhe. Vamos codificar o botão de OK do
alerta de que o usuário foi criado com sucesso. Assim, quando o botão OK for
pressionado o alerta fechará e retornará para a tela de Login.
8. Vamos voltar para nossa classe Register e alterar o método
_createUserAsync de acordo com o trecho de código apresentado na
Tabela 3.42 – Voltando para o Login após usuário criado com sucesso;
60
JavaScript ... async _createUserAsync() { try { const user = await createUserOnFirebaseAsync(this.state.email, this.state.password); Alert.alert("User Created", `User ${user.email} has succesfuly been created!`, [{ text: 'Ok', onPress: () => { this.props.navigation.goBack(); } }]); } catch (error) { Alert.alert('Create User Failed!', error.message); } } ...
Tabela 3.42 – Voltando para o Login após usuário criado com sucesso
Pronto! Agora podemos testar o registro de usuário.
Figura 3.18 – Registrando usuário no iPhone
Figura 3.19 – Registrando usuário no Android
Neste subcapítulo alteramos três classes. Vamos ver o resultado das classes
através das Tabela 3.43 - Classe FirebaseApi com o método
createUserOnFirebaseAsync, Tabela 3.44 – Inicialização na biblioteca do Firebase
através da classe index e Tabela 3.45 – Registrando usuário na classe Register.
JavaScript import firebase from 'firebase';
61
const config = { apiKey: "AIzaSyDtVC3VQ1Z8XzQYDkWwnTOC_NFo8ny5c90", authDomain: "todomanager-5444a.firebaseapp.com", databaseURL: "https://todomanager-5444a.firebaseio.com", projectId: "todomanager-5444a", storageBucket: "todomanager-5444a.appspot.com", messagingSenderId: "254572727152" }; export const initializeFirebaseApi = () => firebase.initializeApp(config); export const createUserOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .createUserWithEmailAndPassword(email, password); return user; }
Tabela 3.43 - Classe FirebaseApi com o método createUserOnFirebaseAsync
JavaScript import React from 'react'; import { AppRegistry, SafeAreaView } from 'react-native'; import Routes from './src/routes/Routes'; import { name as appName } from './app.json'; import { initializeFirebaseApi } from './src/services/FirebaseApi'; const wrappedRoutes = () => { return ( <SafeAreaView style={{ flex: 1 }}> <Routes /> </SafeAreaView> ); }; AppRegistry.registerComponent(appName, () => { initializeFirebaseApi(); return wrappedRoutes; });
Tabela 3.44 – Inicialização na biblioteca do Firebase através da classe index
JavaScript import React, { Component } from 'react'; import { KeyboardAvoidingView, View, Image, Text, TextInput, Button, StyleSheet, Alert } from 'react-native'; import { createUserOnFirebaseAsync } from '../services/FirebaseApi'; const img = require('../assets/TodoList.png'); export default class Register extends Component { static navigationOptions = { title: 'Register' }; state = { email: '', password: '' } render() {
62
return ( <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> <Text style={styles.title}>Registering new user</Text> </View> <View style={styles.bottomView}> <TextInput style={styles.input} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' onChangeText={email => { this.setState({ email }) }} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={password => this.setState({ password })} /> <Button title='Register User' onPress={() => this._createUserAsync()} /> </View> </KeyboardAvoidingView> ); } async _createUserAsync() { try { const user = await createUserOnFirebaseAsync(this.state.email, this.state.password); Alert.alert("User Created", `User ${user.email} has succesfuly been created!`, [{ text: 'Ok', onPress: () => { this.props.navigation.goBack(); } }]); } catch (error) { Alert.alert('Create User Failed!', error.message); } } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', justifyContent: 'center' }, topView: { flex: 0.20, flexDirection: 'row', alignItems: 'center', padding: 25 }, img: { width: 50, height: 50 }, title: {
63
fontSize: 20, fontWeight: 'bold', marginLeft: 20 }, bottomView: { flex: 1, flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 } });
Tabela 3.45 – Registrando usuário na classe Register
3.10 AUTENTICANDO USUÁRIO
No subcapítulo anterior codificamos o registro de um novo usuário. Neste
subcapítulo, vamos utilizar esse usuário registrado no processo de autenticação. Vamos
alterar duas classes: FirebaseApi.js e Login.js.
1. Com o nosso projeto aberto no VS Code, vamos abrir o arquivo
FirebaseApi.js localizado dentro da pasta src/services/; 2. Com o arquivo FirebaseApi.js aberto, vamos inserir o código disponível
na Tabela 3.46 – Método para autenticar usuário no Firebase abaixo do
método createUserOnFirebaseAsync;
JavaScript ... export async function signInOnFirebaseAsync(email, password) { const user = await firebase.auth().signInWithEmailAndPassword(email, password); return user; } ...
Tabela 3.46 – Método para autenticar usuário no Firebase
O método signInOnFirebaseAsync recebe como parâmetro o email e
password e repassa esses parâmetros invocando o método
signInWithEmailAndPassword da biblioteca do Firebase. O método nos retorna uma
promessa de que o usuário será autenticado, mas caso ocorra algum erro, a promessa
repassa esse erro. Então, para saber se o usuário foi autenticado com sucesso
precisamos implementar outro método.
3. Vamos abrir outro arquivo o Login.js localizado dentro da pasta
src/screens/;
64
4. Com o arquivo Login.js aberto, vamos começar nossa codificação
importando o componente Alert da biblioteca ‘react-native’ e o método
signInOnFirebaseAsync da classe FirebaseApi;
JavaScript ... import React, { Component } from 'react'; import { SafeAreaView, KeyboardAvoidingView, StyleSheet, View, Image, TextInput, Button, Text, Alert } from 'react-native'; import { signInOnFirebaseAsync } from '../services/FirebaseApi'; ...
Tabela 3.47 – Importando o método signInOnFirebase da classe FirebaseApi
5. Ainda na classe Login, vamos criar o método _signInAsync abaixo do
método render;
JavaScript ... async signInAsync() { try { const user = await signInOnFirebaseAsync(this.state.email, this.state.password); Alert.alert("User Authenticated", `User ${user.email} has succesfuly been authenticated!`); } catch (error) { Alert.alert("Login Failed", error.message); } } ...
Tabela 3.48 [Método createUser da classe Register]
O método _signInAsync executa o método signInOnFirebaseAsync
disponível na classe FirebaseApi. O método signInOnFirebaseAsync recebe como
parâmetro o email e password e nos retorna uma promessa de que o usuário será
autenticado, mas caso ocorra algum erro, a promessa repassa esse erro. Por enquanto,
em ambos os casos, codificamos uma mensagem de alerta para ser exibida, ou seja, em
caso de sucesso a mensagem “User has succesfuly been authenticated” será exibida,
caso contrário, uma mensagem de erro aparecerá.
6. Também precisamos alterar o evento onPress de nosso componente
<Button/>. Temos que executar o método _signInAsync quando o botão
Sign In for pressionado;
JavaScript ... render() { return (
65
<KeyboardAvoidingView style={styles.container} behavior='padding'> ... <Button title='Sign In' onPress={() => this._signInAsync()} /> ... </KeyboardAvoidingView> ); } ...
Tabela 3.49 – Evento onPress do botão Register User
Pronto! Já podemos testar nosso código. Na tela de Login, se preenchermos
os dados de e-mail e senha utilizados no registro do usuário e pressionarmos no botão
Sign In, teremos o nosso usuário autenticado com sucesso. Observe as figuras Figura
3.20 – Autenticando usuário no iPhone e Figura 3.21 – Autenticando usuário no Android.
Figura 3.20 – Autenticando usuário no iPhone
Figura 3.21 – Autenticando usuário no Android
Neste subcapítulo alteramos duas classes. Vamos ver o resultado das classes
através da Tabela 3.50 – Classe FirebaseApi com o método signInOnFirebase e Tabela
3.51 – Autenticando usuário na classe Login.
JavaScript import firebase from 'firebase'; const config = { apiKey: "AIzaSyDtVC3VQ1Z8XzQYDkWwnTOC_NFo8ny5c90", authDomain: "todomanager-5444a.firebaseapp.com", databaseURL: "https://todomanager-5444a.firebaseio.com", projectId: "todomanager-5444a",
66
storageBucket: "todomanager-5444a.appspot.com", messagingSenderId: "254572727152" }; export const initializeFirebaseApi = () => firebase.initializeApp(config); export const createUserOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .createUserWithEmailAndPassword(email, password); return user; } export const signInOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .signInWithEmailAndPassword(email, password); return user; }
Tabela 3.50 – Classe FirebaseApi com o método signInOnFirebase
JavaScript import React, { Component } from 'react'; import { StyleSheet, KeyboardAvoidingView, View, Image, TextInput, Button, Text, Alert } from 'react-native'; import { signInOnFirebaseAsync } from '../services/FirebaseApi'; const img = require('../assets/TodoList.png'); export default class Login extends Component { static navigationOptions = { header: null }; state = { email: this.props.email, password: '' }; render() { return ( <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> </View> <View style={styles.bottomView}> <TextInput style={styles.input} value={this.state.email} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' onChangeText={(text) => this.setState({ email: text })} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={(text) => this.setState({ password: text })} />
67
<Button title='Sign In' onPress={() => this._signInAsync()} /> <View style={styles.textConteiner}> <Text>Not a member? Let's </Text> <Text style={styles.textRegister} onPress={() => { const { navigate } = this.props.navigation; navigate('pageRegister'); }}> Register </Text> </View> </View> </KeyboardAvoidingView> ); } async _signInAsync() { try { const user = await signInOnFirebaseAsync(this.state.email, this.state.password); Alert.alert("User Authenticated", `User ${user.email} has succesfuly been authenticated!`); } catch (error) { Alert.alert("Login Failed", error.message); } } } const styles = StyleSheet.create({ container: { flex: 1 }, topView: { justifyContent: 'center', alignItems: 'center', padding: 50 }, img: { width: 200, height: 200 }, bottomView: { flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 }, textConteiner: { flexDirection: 'row', justifyContent: 'center', marginTop: 20 }, textRegister: { fontWeight: 'bold' } });
Tabela 3.51 – Autenticando usuário na classe Login
68
3.11 TELA DE LISTAGEM
Neste subcapítulo vamos iniciar a construção de nossa tela de listagem de
tarefas. Inicialmente, vamos construir somente a “casca” da tela de listagem.
1. Com o nosso projeto aberto no VS Code, vamos criar os arquivos
ToDoTasks.js e DoneTasks.js dentro da pasta src/screens/; 2. Com o arquivo ToDoTasks.js aberto, vamos criar a classe ToDoTasks;
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View } from 'react-native';
const imgChecList = require('../assets/checklist.png');
export default class ToDoTasks extends Component { static navigationOptions = { tabBarLabel: 'To Do', tabBarIcon: ({ tintColor }) => ( <Image source={imgChecList} style={[styles.icon, { tintColor }]} /> ) } ... } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 } });
Tabela 3.52 – Criando a classe ToDoTasks
Por enquanto, não codificamos muito em nossa classe ToDoTasks. Como
nossa tela ToDoTasks será uma aba da rota TasksList, definimos a imagem e o título
da aba. Essas configurações são específicas para o iOS. Também, definimos alguns
estilos que utilizaremos neste subcapítulo.
69
3. Vamos codificar o método render. Por enquanto, o método render será
simples contendo apenas um botão flutuante caso a plataforma for
Android;
JavaScript ... render() { return ( <View style={styles.container} /> ); } ...
Tabela 3.53 – Criando floatButton na classe ToDoTasks
4. Agora, vamos criar o arquivo DoneTasks.js e praticamente replicar nossa
classe ToDoTasks para a classe DoneTasks. Com o arquivo
DoneTasks.js aberto, vamos criar a classe DoneTasks;
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View } from 'react-native'; const imgDone = require('../assets/done.png'); export default class DoneTasks extends Component { static navigationOptions = { tabBarLabel: 'Done', tabBarIcon: ({ tintColor }) => (<Image source={imgDone} style={[styles.icon, { tintColor: tintColor }]} />) } ... } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 } });
Tabela 3.54 – Criando a classe DoneTasks
70
5. Vamos codificar o método render e adicionar um botão flutuante;
JavaScript ... render() { return ( <View style={ styles.conteiner }/> ); } ...
Tabela 3.55 – Criando floatButton na classe DoneTasks
Pronto! Nossas classes que serão as abas de nossa rota TasksList já estão
criadas. Vamos codificar a rota, mas antes, temos que importar nossas páginas no
arquivo de Screens.
6. Vamos abrir o arquivo Screens.js localizado na pasta src/screens e
importar as páginas ToDoTasks e DoneTasks;
JavaScript import Login from './Login'; import Register from './Register'; import ToDoTasks from './ToDoTasks'; import DoneTasks from './DoneTasks'; export { Login, Register, ToDoTasks, DoneTasks }
Tabela 3.56 - Importando as novas páginas no arquivo Screens
7. Também precisaremos abrir o arquivo Routes.js localizado na pasta
src/services e definir uma constante para nosso componente
TabNavigator com duas abas;
JavaScript import { createStackNavigator, createTabNavigator } from 'react-navigation'; import { Login, Register, ToDoTasks, DoneTasks } from '../screens/Screens'; const taskListTabNavigator = createTabNavigator({ pageToDoTasks: { screen: ToDoTasks, title: 'To Do' }, pageDoneTasks: { screen: DoneTasks, title: 'Done' } }); ...
Tabela 3.57 – Definindo a TabNavigator
O componente TabNavigator será composto por duas abas: aba com o título
‘To Do’ representada pela classe ToDoTasks; aba com o título ‘Done’ representada
pela classe DoneTasks.
71
8. Agora podemos definir a rota TasksList;
JavaScript ... export default Routes = createStackNavigator( { pageLogin: { screen: Login }, pageRegister: { screen: Register }, pageTasksList: { screen: taskListTabNavigator, navigationOptions: { ...Platform.select({ ios: { title: 'Task List' }, android: { header: null } }) } } }, { headerMode: 'screen' } );
Tabela 3.58 – Rota para TasksList
A definição de uma rota composta por duas telas se da através da função
createTabNavigator. Essa função configura uma navegação em abas. Além disso,
utilizamos um recurso novo, isto é, utilizamos o objeto Platform para configurar
características diferentes para Android ou iOS. Neste caso, no iOS existirá o título
‘Tasks List’ na página TasksList e no Android removemos a barra de título.
Para testarmos, temos que alterar o método _signInAsync da classe Login.
Após realizar a autenticação vamos limpar o histórico de navegação e navegar para
TaskList.
9. Vamos abrir o arquivo Login.js localizado na pasta src/screens e alterar
o código do método _signInAsync de acordo com o trecho de código da
Tabela 3.59 - Método _signInAsync alterado;
JavaScript import { StackActions, NavigationActions } from 'react-navigation'; ... async signInAsync() { try { const user = await signInOnFirebaseAsync(this.state.email, this.state.password);
72
const resetNavigation = StackActions.reset({ index: 0, actions: [NavigationActions.navigate({ routeName: 'pageTasksList' })] }); this.props.navigation.dispatch(resetNavigation); } catch (error) { Alert.alert("Login Failed", error.message); } } ...
Tabela 3.59 - Método _signInAsync alterado
Importamos os componentes StackActions e NavigationActions e
implementamos a navegação para a tela TasksList. Agora, quando realizarmos a
autenticação de nosso usuário limpamos o histórico de navegação e navegamos para a
página TasksList. Neste cenário, se não limparmos o histórico de navegação a tela
TasksList com as abas ToDo e Done será apresentada com a opção voltar no canto
superior esquerdo. Remover essa opção faz todo sentido pois com o usuário autenticado
nossa página inicial passa a ser a TasksList.
Observe o resultado de nossa codificação através das Figura 3.22 – Tela de
Listagem das Tarefas no iPhone e Figura 3.23 – Tela de Listagem das Tarefas no Android.
Figura 3.22 – Tela de Listagem das Tarefas no iPhone
Figura 3.23 – Tela de Listagem das Tarefas no Android
Neste subcapítulo alteramos cinco arquivos. Vamos ver os resultados dessas
alterações através da Tabela 3.60 – Aba ToDo representada pela classe ToDoTasks,
73
Tabela 3.61 – Aba Done representada pela classe DoneTasks, Tabela 3.62 – Arquivo
Screens ajustado, Tabela 3.63 – TasksList com as abas ToDo e Done e Tabela 3.64 -
Classe Login com autenticação e navegação.
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View } from 'react-native'; const imgChecList = require('../assets/checklist.png'); export default class ToDoTasks extends Component { static navigationOptions = { tabBarLabel: 'To Do', tabBarIcon: ({ tintColor }) => ( <Image source={imgChecList} style={[styles.icon, { tintColor }]} /> ) } render() { return ( <View style={styles.container} /> ); } } const styles = StyleSheet.create({ container: { flex: 1, paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 } });
Tabela 3.60 – Aba ToDo representada pela classe ToDoTasks
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View } from 'react-native'; const imgDone = require('../assets/done.png'); export default class DoneTasks extends Component { static navigationOptions = { tabBarLabel: 'Done', tabBarIcon: ({ tintColor }) => ( <Image source={imgDone} style={[styles.icon, { tintColor }]} /> )
74
} render() { return ( <View style={styles.container} /> ); } } const styles = StyleSheet.create({ container: { flex: 1, paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 } });
Tabela 3.61 – Aba Done representada pela classe DoneTasks
JavaScript import Login from './Login'; import Register from './Register'; import ToDoTasks from './ToDoTasks'; import DoneTasks from './DoneTasks'; export { Login, Register, ToDoTasks, DoneTasks }
Tabela 3.62 – Arquivo Screens ajustado
JavaScript import { createStackNavigator, createTabNavigator } from 'react-navigation'; import { Login, Register, ToDoTasks, DoneTasks } from '../screens/Screens'; import { Platform } from 'react-native'; const taskListTabNavigator = createTabNavigator({ pageToDoTasks: { screen: ToDoTasks, title: 'To Do' }, pageDoneTasks: { screen: DoneTasks, title: 'Done' } }); export default Routes = createStackNavigator( { pageLogin: { screen: Login }, pageRegister: { screen: Register }, pageTasksList: { screen: taskListTabNavigator, navigationOptions: { ...Platform.select({ ios: { title: 'Task List' },
75
android: { header: null } }) } } }, { headerMode: 'screen' } );
Tabela 3.63 – TasksList com as abas ToDo e Done
JavaScript import React, { Component } from 'react'; import { StyleSheet, KeyboardAvoidingView, View, Image, TextInput, Button, Text, Alert } from 'react-native'; import { signInOnFirebaseAsync } from '../services/FirebaseApi'; import { StackActions, NavigationActions } from 'react-navigation'; const img = require('../assets/TodoList.png'); export default class Login extends Component { static navigationOptions = { header: null }; state = { email: this.props.email, password: '' }; render() { return ( <KeyboardAvoidingView style={styles.container} behavior='padding'> <View style={styles.topView}> <Image style={styles.img} source={img} /> </View> <View style={styles.bottomView}> <TextInput style={styles.input} value={this.state.email} placeholder='Email' keyboardType={'email-address'} autoCapitalize='none' onChangeText={(text) => this.setState({ email: text })} /> <TextInput style={styles.input} placeholder='Password' secureTextEntry={true} onChangeText={(text) => this.setState({ password: text })} /> <Button title='Sign In' onPress={() => this._signInAsync()} /> <View style={styles.textConteiner}> <Text>Not a member? Let's </Text> <Text style={styles.textRegister} onPress={() => { const { navigate } = this.props.navigation; navigate('pageRegister'); }}>
76
Register </Text> </View> </View> </KeyboardAvoidingView> ); } async _signInAsync() { try { const user = await signInOnFirebaseAsync(this.state.email, this.state.password); const resetNavigation = StackActions.reset({ index: 0, actions: [NavigationActions.navigate({ routeName: 'pageTasksList' })] }); this.props.navigation.dispatch(resetNavigation); } catch (error) { Alert.alert("Login Failed", error.message); } } } const styles = StyleSheet.create({ container: { flex: 1 }, topView: { justifyContent: 'center', alignItems: 'center', padding: 50 }, img: { width: 200, height: 200 }, bottomView: { flexDirection: 'column', paddingRight: 20, paddingLeft: 20 }, input: { marginBottom: 20 }, textConteiner: { flexDirection: 'row', justifyContent: 'center', marginTop: 20 }, textRegister: { fontWeight: 'bold' } });
Tabela 3.64 - Classe Login com autenticação e navegação
77
3.12 VERIFICANDO O ESTADO DO USUÁRIO
Nossa aplicação já está tomando corpo. Agora, ao inicializar nossa aplicação
iremos verificar se o usuário já se encontra autenticado. Caso não esteja autenticado,
vamos navegar à tela de Login. Caso contrário, iremos navegar à tela TasksList.
1. Com o nosso projeto aberto no VS Code, vamos abrir o arquivo
FirebaseApi.js localizado dentro da pasta src/services/; 2. Com o arquivo FirebaseApi.js aberto, vamos criar a função
currentFirebaseUser para verificar o estado do usuário;
JavaScript export const currentFirebaseUser = () => { return new Promise((resolve, reject) => { var unsubscribe = null; unsubscribe = firebase .auth() .onAuthStateChanged((user) => { resolve(user); }, (error) => { reject(error); }, () => { unsubscribe(); }); }); }
Tabela 3.65 – Método para autenticar usuário no Firebase
O método currentFirebaseUser nos retorna uma promessa indicando qual
usuário está autenticado, mas caso ocorra algum erro, significa que não existe usuário
algum autenticado. Essa verificação é realizada através do método
onAuthStateChanged da API do Firebase que implementa o padrão Publisher-Subscriber, ou seja, o método onAuthStateChanged avisa aos seus assinantes sobre
qualquer alteração do usuário e para receber esse aviso precisamos assiná-lo. Então,
nós assinamos esse método e quando o mesmo concluí sua execução, cancelamos essa
assinatura.
A assinatura do método onAuthStateChanged ocorre quando passamos
como parâmetro um observer representado pela arrow function (user) => { ... }. O
método onAuthStateChanged retorna uma referência dessa assinatura. No nosso caso,
guardamos a referência dessa assinatura na variável unsubscribe e vamos utilizá-la
para cancela essa assinatura assim que o método onAuthStateChanged retornar algo.
78
Agora que estudamos como funciona o método onAuthStateChanged precisamos ajustar a inicialização de nossa aplicação.
3. Vamos criar o arquivo App.js dentro da pasta src/screens e implementar
a classe App de acordo com o trecho de código da Tabela 3.66 –
Implementando a classe App;
JavaScript import React, { Component } from 'react'; import { View, ActivityIndicator, StyleSheet } from 'react-native'; export default class App extends Component { static navigationOptions = { header: null }; render() { return ( <View style={styles.container}> <ActivityIndicator style={styles.loading} /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, loading: { width: 50, height: 50 } });
Tabela 3.66 – Implementando a classe App
4. Agora, vamos ajustar os imports e adicionar o método
componentDidMount de acordo com o código apresentado na Tabela
3.67 – Verificando o estado do usuário;
JavaScript ... import { StackActions, NavigationActions } from 'react-navigation'; import { currentFirebaseUser } from '../services/FirebaseApi'; export default class App extends Component { ... async componentDidMount() { let resetNavigation = StackActions.reset({ index: 0,
79
actions: [NavigationActions.navigate({ routeName: 'pageLogin' })] }); try { const user = await currentFirebaseUser(); if (user) { resetNavigation = StackActions.reset({ index: 0, actions: [NavigationActions.navigate({ routeName: 'pageTasksList' })] }); this.props.navigation.dispatch(resetNavigation); } this.props.navigation.dispatch(resetNavigation); } catch (error) { this.props.navigation.dispatch(resetNavigation); } } }
Tabela 3.67 – Verificando o estado do usuário
No código apresentado acima ajustamos os imports e adicionamos o método
componentDidMount para executar a função currentFirebaseUser disponível na
classe FirebaseApi.js. Essa função nos retorna o usuário atual. Se não houver usuário
algum autenticado, a aplicação navega para a tela de Login, caso contrário, navega à
tela TasksList.
Falta pouco para testarmos nossa aplicação. Precisamos ajustar o imports do
arquivo Screens.js e adicionar a página App em nossas rotas.
5. Vamos abrir o arquivo Screens.js localizado na pasta src/screens e
importar a classe App e, em seguida, exportá-la;
JavaScript import App from './App'; ... export { App, Login, Register, ToDoTasks, DoneTasks }
Tabela 3.68 - Importando a tela App no arquivo Screens
6. Agora, vamos abrir o arquivo Routes.js localizado na pasta src/routes;
7. Com o arquivo aberto, vamos importar a classe App e adicioná-la em
nossas rotas. Observe o trecho de código Tabela 3.69 – Ajustando as rotas.
JavaScript
80
... import { App, Login, Register, ToDoTasks, DoneTasks } from '../screens/Screens'; ... export default Routes = createStackNavigator( { pageApp: { screen: App }, pageLogin: { screen: Login }, pageRegister: { screen: Register }, pageTasksList: { screen: taskListTabNavigator, navigationOptions: { ...Platform.select({ ios: { title: 'Task List' }, android: { header: null } }) } } }, { headerMode: 'screen' } );
Tabela 3.69 – Ajustando as rotas
Pronto! Agora vamos testar nossa aplicação. Agora, ao inicializar a aplicação
visualizaremos a tela App com uma ampulheta ao centro. Se existir algum usuário
autenticado a aplicação apresentará a tela de TasksList, caso contrário, veremos a tela
de Login.
Neste subcapítulo alteramos quatro classes. Vamos ver o resultado das
classes através da Tabela 3.70 – Método currentFirebaseUser que retorna o estado do
usuário, Tabela 3.71 – Navegação de acordo com o estado do usuário, Tabela 3.72 -
Screens.js ajustado e Tabela 3.73 – Adicionando página App nas rotas.
JavaScript import firebase from 'firebase'; const config = { apiKey: "AIzaSyDtVC3VQ1Z8XzQYDkWwnTOC_NFo8ny5c90", authDomain: "todomanager-5444a.firebaseapp.com", databaseURL: "https://todomanager-5444a.firebaseio.com", projectId: "todomanager-5444a", storageBucket: "todomanager-5444a.appspot.com", messagingSenderId: "254572727152" }; export const initializeFirebaseApi = () => firebase.initializeApp(config);
81
export const createUserOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .createUserWithEmailAndPassword(email, password); return user; } export const signInOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .signInWithEmailAndPassword(email, password); return user; } export const currentFirebaseUser = () => { return new Promise((resolve, reject) => { var unsubscribe = null; unsubscribe = firebase .auth() .onAuthStateChanged((user) => { resolve(user); }, (error) => { reject(error); }, () => { unsubscribe(); }); }); }
Tabela 3.70 – Método currentFirebaseUser que retorna o estado do usuário
JavaScript import React, { Component } from 'react'; import { View, ActivityIndicator, StyleSheet } from 'react-native'; import { StackActions, NavigationActions } from 'react-navigation'; import { currentFirebaseUser } from '../services/FirebaseApi'; export default class App extends Component { static navigationOptions = { header: null }; render() { return ( <View style={styles.container}> <ActivityIndicator style={styles.loading} /> </View> ); } async componentDidMount() { let resetNavigation = StackActions.reset({ index: 0, actions: [NavigationActions.navigate({ routeName: 'pageLogin' })] }); try { const user = await currentFirebaseUser(); if (user) {
82
resetNavigation = StackActions.reset({ index: 0, actions: [NavigationActions.navigate({ routeName: 'pageTasksList' })] }); this.props.navigation.dispatch(resetNavigation); } this.props.navigation.dispatch(resetNavigation); } catch (error) { this.props.navigation.dispatch(resetNavigation); } } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, loading: { width: 50, height: 50 } });
Tabela 3.71 – Navegação de acordo com o estado do usuário
JavaScript import App from './App'; import Login from './Login'; import Register from './Register'; import ToDoTasks from './ToDoTasks'; import DoneTasks from './DoneTasks'; export { App, Login, Register, ToDoTasks, DoneTasks }
Tabela 3.72 - Screens.js ajustado
JavaScript import { createStackNavigator, createTabNavigator } from 'react-navigation'; import { App, Login, Register, ToDoTasks, DoneTasks } from '../screens/Screens'; import { Platform } from 'react-native'; const taskListTabNavigator = createTabNavigator({ pageToDoTasks: { screen: ToDoTasks, title: 'To Do' }, pageDoneTasks: { screen: DoneTasks, title: 'Done' } }); export default Routes = createStackNavigator( { pageApp: { screen: App }, pageLogin: { screen: Login }, pageRegister: { screen: Register }, pageTasksList: { screen: taskListTabNavigator, navigationOptions: {
83
...Platform.select({ ios: { title: 'Task List' }, android: { header: null } }) } } }, { headerMode: 'screen' } );
Tabela 3.73 – Adicionando página App nas rotas
3.13 TELA DE TAREFA
Neste subcapítulo, vamos construir a tela para incluir uma nova tarefa no
Firebase. Também iremos utilizar essa mesma tela para salvar alterações de uma tarefa.
No entanto, salvar essas alterações será assunto de um subcapítulo específico. Neste
subcapítulo vamos focar somente na inclusão.
1. Com o nosso projeto aberto no VS Code, vamos abrir o arquivo
FirebaseApi.js localizado dentro da pasta src/services/; 2. Com o arquivo FirebaseApi.js aberto, vamos criar o método
writeTaskOnFirebase;
JavaScript export const writeTaskOnFirebaseAsync = async (task) => { const user = await currentFirebaseUser(); var tasksReference = firebase .database() .ref(user.uid); const key = tasksReference .child('tasks') .push() .key; return await tasksReference .child(`tasks/${key}`) .update(task); }
Tabela 3.74 – Método para criar Task no Firebase
O método writeTaskOnFirebase recebe o parâmetro task – que é
representado por um json – e repassa esse parâmetro invocando o método update da
biblioteca do Firebase. O método nos retorna uma promessa de que a Task será criada,
84
mas caso ocorra algum erro a promessa repassa esse erro. As Tasks são salvas
somente para o usuário corrente, ou seja, cada usuário visualiza somente suas Tasks.
Esse controle é realizado através do método
tasksReference.database().ref(user.uid) que recebe como parâmetro o usuário atual
autenticado.
3. Dando continuidade em nossa codificação, vamos criar o arquivo Task.js
dentro da pasta src/screens/; 4. Com o arquivo Task.js aberto, vamos cria a classe Task e definir alguns
estilos;
JavaScript import React, { Component } from 'react'; import { View, TextInput, Switch, Text, Button, StyleSheet } from 'react-native'; export default class Task extends Component { static navigationOptions = { title: 'Task' } ... } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', padding: 20, }, input: { marginBottom: 20 }, multilineInput: { height: 100 }, switchContainer: { flexDirection: 'row', alignItems: 'center', paddingBottom: 20 }, switchText: { marginLeft: 10, color: 'black', fontSize: 18 }
85
}); Tabela 3.75 – Definindo a classe Task
5. Vamos definir os states;
JavaScript ... state = { title: '', resume: '', priority: true, isDone: false }; ...
Tabela 3.76 - Definindo os states da classe Task
6. Vamos definir o método _saveTaskAsync e importar o método
writeTaskOnFirebase;
JavaScript ... import { writeTaskOnFirebaseAsync } from '../services/FirebaseApi'; ... async _saveTaskAsync() { var task = { title: this.state.title, resume: this.state.resume, priority: this.state.priority, isDone: this.state.isDone }; try { await writeTaskOnFirebaseAsync(task); this.props.navigation.goBack(); } catch (error) { Alert.alert("Erro Saving", error.message); } } ...
Tabela 3.77 [Método saveTask da classe Task]
O método _saveTaskAsync executa o método writeTaskOnFirebaseAsync
disponível na classe FirebaseApi. O método writeTaskOnFirebaseAsync recebe como
parâmetro um objeto que representa uma Task e nos retorna uma promessa de que a
Task será criada, mas caso ocorra algum erro a promessa repassa esse erro.
7. Por enquanto, o último trabalho em nossa classe Task é codificar o método
render;
JavaScript ... render() {
86
return ( <View style={styles.container}> <TextInput style={styles.input} placeholder='Title' value={this.state.title} onChangeText={(value) => this.setState({ title: value })} /> <TextInput style={[styles.input, styles.multilineInput]} placeholder='Resume' multiline={true} numberOfLines={4} value={this.state.resume} onChangeText={(value) => this.setState({ resume: value })} /> <View style={styles.switchContainer}> <Switch value={this.state.priority} onValueChange={(value) => this.setState({ priority: value })} value={this.state.priority} /> <Text style={styles.switchText}>Hight Priority</Text> </View> <View style={styles.switchContainer}> <Switch value={this.state.isDone} onValueChange={(value) => this.setState({ isDone: value })} value={this.state.isDone} /> <Text style={styles.switchText}>Is Done?</Text> </View> <Button style={styles.button} title='Save' onPress={() => this._saveTaskAsync()} /> </View> ); } ...
Tabela 3.78 – Método render da classe Task
Antes de seguirmos com a codificação de nossa classe Task, e como forma
de exercício para fixarmos o entendimento do flexbox, vamos entender como ficou a
hierarquia de nossa classe:
• View principal que ocupa a tela inteira e seus componentes filhos são
posicionados um abaixo do outro:
o TextInput que posicionado no topo da tela;
o TextInput que posicionado abaixo do primeiro TextInput; o View que é posicionada abaixo do segundo TextInput, contendo
dois componentes que serão posicionados horizontalmente:
§ Switch que é alinhado ao lado esquerdo;
§ Text posicionado ao lado direito do componente Switch.
o View que é posicionada abaixo da primeira View filha, contendo
dois componentes que serão posicionados horizontalmente:
§ Switch que é alinhado ao lado esquerdo;
§ Text posicionado ao lado direito do componente Switch.
87
o Button que é posicionado como último elemento, abaixo da última
View filha.
8. Vamos abrir nosso arquivo Screens.js localizado na pasta src/screens e
incluir a página Task em nosso esquema de import e export;
JavaScript ... import Task from './Task'; export { App, Login, Register, ToDoTasks, DoneTasks, Task }
Tabela 3.79 – Ajustando o esquema de import e export das páginas
9. Agora, temos que adicionar uma rota para nossa tela Task. Vamos abrir o
arquivo Routes.js localizado na pasta src/services e codificar essa nova
rota para nossa tela Task;
JavaScript ... import { App, Login, Register, ToDoTasks, DoneTasks, Task } from '../screens/Screens'; ... export default Routes = createStackNavigator( { pageApp: { screen: App }, pageLogin: { screen: Login }, pageRegister: { screen: Register }, pageTasksList: { screen: taskListTabNavigator, navigationOptions: { ...Platform.select({ ios: { title: 'Task List' }, android: { header: null } }) } }, pageTask: { screen: Task } }, { headerMode: 'screen' } );
Tabela 3.80 – Adicionando uma nova rota
88
10. Precisamos navegar para a tela Task. Vamos abrir o arquivo
ToDoTasks.js, implementar o método _goToTask;
JavaScript ... _goToTask() { this.props.navigation.navigate('pageTask'); } ...
Tabela 3.81 [Método createTask que navega para Task]
11. Por último, vamos adicionar um botão no rodapé dá página ToDoTasks
que quando clicarmos vamos navegar para a página de Task. Observe
que temos que importar o componente TouchableOpacity, definir uma
imagem e adicionar um novo estilo.
JavaScript ... import { Image, StyleSheet, View, TouchableOpacity } from 'react-native'; const imgCheckList = require('../assets/checklist.png'); const imgPlus = require('../assets/plus_64.png'); export default class ToDoTasks extends Component { ... render() { return ( <View style={styles.container}> <TouchableOpacity style={styles.floatButton} onPress={() => this._goToTask()}> <Image source={imgPlus} style={styles.img} /> </TouchableOpacity> </View> ); } ... } const styles = StyleSheet.create({ container: { flex: 1, paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 },
89
floatButton: { position: 'absolute', right: 20, bottom: 20 } });
Tabela 3.82 – Evento onPress do float button
Podemos observar o resultado do nosso código através das imagens Error! Reference source not found. e Error! Reference source not found..
Figura 3.24 – Tela de Task no iPhone
Figura 3.25 – Tela de Task no Android
Neste subcapítulo alteramos quatro arquivos e criamos uma nova página.
Vamos ver o resultado desse trabalho através dos trechos de códigos apresentados na
Tabela 3.83 – Método writeTaskOnFirebaseAsync para criar Tasks, Tabela 3.84 – Classe
Task, Tabela 3.84 – Classe Task, Tabela 3.85 - Arquivo Screens com a página Task,
Tabela 3.86 – Definição da rota para Task e Tabela 3.87 – Navegando para tela Task.
JavaScript import firebase from 'firebase'; const config = { apiKey: "AIzaSyDtVC3VQ1Z8XzQYDkWwnTOC_NFo8ny5c90", authDomain: "todomanager-5444a.firebaseapp.com", databaseURL: "https://todomanager-5444a.firebaseio.com", projectId: "todomanager-5444a", storageBucket: "todomanager-5444a.appspot.com", messagingSenderId: "254572727152" };
90
export const initializeFirebaseApi = () => firebase.initializeApp(config); export const createUserOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .createUserWithEmailAndPassword(email, password); return user; } export const signInOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .signInWithEmailAndPassword(email, password); return user; } export const currentFirebaseUser = () => { return new Promise((resolve, reject) => { var unsubscribe = null; unsubscribe = firebase .auth() .onAuthStateChanged((user) => { resolve(user); }, (error) => { reject(error); }, () => { unsubscribe(); }); }); } export const writeTaskOnFirebaseAsync = async (task) => { const user = await currentFirebaseUser(); var tasksReference = firebase .database() .ref(user.uid); const key = tasksReference .child('tasks') .push() .key; return await tasksReference .child(`tasks/${key}`) .update(task); }
Tabela 3.83 – Método writeTaskOnFirebaseAsync para criar Tasks
JavaScript import React, { Component } from 'react'; import { View, TextInput, Switch, Text, Button, StyleSheet } from 'react-native'; import { writeTaskOnFirebase, writeTaskOnFirebaseAsync } from '../services/FirebaseApi'; export default class Task extends Component { static navigationOptions = { title: 'Task' }
91
state = { title: '', resume: '', priority: true, isDone: false }; render() { return ( <View style={styles.container}> <TextInput style={styles.input} placeholder='Title' value={this.state.title} onChangeText={(value) => this.setState({ title: value })} /> <TextInput style={[styles.input, styles.multilineInput]} placeholder='Resume' multiline={true} numberOfLines={4} value={this.state.resume} onChangeText={(value) => this.setState({ resume: value })} /> <View style={styles.switchContainer}> <Switch value={this.state.priority} onValueChange={(value) => this.setState({ priority: value })} value={this.state.priority} /> <Text style={styles.switchText}>Hight Priority</Text> </View> <View style={styles.switchContainer}> <Switch value={this.state.isDone} onValueChange={(value) => this.setState({ isDone: value })} value={this.state.isDone} /> <Text style={styles.switchText}>Is Done?</Text> </View> <Button style={styles.button} title='Save' onPress={async () => this._saveTaskAsync()} /> </View> ); } async _saveTaskAsync() { var task = { title: this.state.title, resume: this.state.resume, priority: this.state.priority, isDone: this.state.isDone }; try { await writeTaskOnFirebaseAsync(task); this.props.navigation.goBack(); } catch (error) { Alert.alert("Erro Saving", error.message); } } } const styles = StyleSheet.create({ container: { flex: 1, padding: 20,
92
}, input: { marginBottom: 20 }, multilineInput: { height: 100 }, switchContainer: { flexDirection: 'row', alignItems: 'center', paddingBottom: 20 }, switchText: { marginLeft: 10, color: 'black', fontSize: 18 } });
Tabela 3.84 – Classe Task
JavaScript import App from './App'; import Login from './Login'; import Register from './Register'; import ToDoTasks from './ToDoTasks'; import DoneTasks from './DoneTasks'; import Task from './Task'; export { App, Login, Register, ToDoTasks, DoneTasks, Task }
Tabela 3.85 - Arquivo Screens com a página Task
JavaScript import { createStackNavigator, createTabNavigator } from 'react-navigation'; import { App, Login, Register, ToDoTasks, DoneTasks, Task } from '../screens/Screens'; import { Platform } from 'react-native'; const taskListTabNavigator = createTabNavigator({ pageToDoTasks: { screen: ToDoTasks, title: 'To Do' }, pageDoneTasks: { screen: DoneTasks, title: 'Done' } }); export default Routes = createStackNavigator( { pageApp: { screen: App }, pageLogin: { screen: Login }, pageRegister: { screen: Register }, pageTasksList: { screen: taskListTabNavigator, navigationOptions: { ...Platform.select({ ios: { title: 'Task List' }, android: {
93
header: null } }) } }, pageTask: { screen: Task } }, { headerMode: 'screen' } );
Tabela 3.86 – Definição da rota para Task
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View, TouchableOpacity } from 'react-native'; const imgCheckList = require('../assets/checklist.png'); const imgPlus = require('../assets/plus_64.png'); export default class ToDoTasks extends Component { static navigationOptions = { tabBarLabel: 'To Do', tabBarIcon: ({ tintColor }) => ( <Image source={imgCheckList} style={[styles.icon, { tintColor }]} /> ) } state = { tasks: [] } render() { return ( <View style={styles.container}> <TouchableOpacity style={styles.floatButton} onPress={() => this._goToTask()}> <Image source={imgPlus} style={styles.img} /> </TouchableOpacity> </View> ); } _goToTask() { this.props.navigation.navigate('pageTask'); } } const styles = StyleSheet.create({ container: { flex: 1, paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26
94
}, img: { width: 50, height: 50 }, floatButton: { position: 'absolute', right: 20, bottom: 20 } });
Tabela 3.87 – Navegando para tela Task
3.14 COMPONENTE DE LISTAGEM
Neste subcapítulo vamos codificar a leitura das tarefas do usuário autenticado
no Firebase. Vamos aproveitar e explorar um conceito muito importante do React: componentizar, ou seja, separar o nosso código o máximo que pudermos.
Vamos começar codificando um componente para nossa listagem de tarefas.
1. Com o nosso projeto aberto no VS Code, vamos criar o arquivo
TaskListView dentro da pasta src/components;
2. Vamos criar o esqueleto de nossa classe TaskListView e deixá-la de
acordo com o código apresentado na Tabela 3.88 - Criando o componente
TaskListView;
JavaScript import React, { Component } from 'react'; import { View, SectionList, Text, TouchableOpacity, StyleSheet } from 'react-native'; export default class TaskListView extends Component { } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', paddingLeft: 10, paddingRight: 10 }, headerConteiner: { flex: 1, flexDirection: 'row', alignItems: 'center', backgroundColor: 'silver', borderRadius: 25, marginTop: 10 }, headerTagConteiner: {
95
backgroundColor: 'gray', height: 35, width: 35, alignItems: 'center', justifyContent: 'center', borderRadius: 25 }, headerTagText: { color: '#FFF', fontSize: 22 }, headerText: { fontSize: 16, marginLeft: 10 }, itemConteiner: { flex: 1, flexDirection: 'column', backgroundColor: '#F3F2F0', marginTop: 5, padding: 10, height: 75 }, itemTextTitle: { fontSize: 22 } });
Tabela 3.88 - Criando o componente TaskListView
Por enquanto, importamos os componentes que utilizamos, definimos nossa
classe e criamos alguns estilos.
3. Vamos codificar o método render. Para facilitar nossa codificação vamos
dividir o método render em mais três trechos de código: métodos _renderSectionHeader, _renderItem e o próprio método render;
JavaScript ... _renderSectionHeader(sectionData) { return ( <View style={styles.headerConteiner}> <View style={styles.headerTagConteiner}> <Text style={styles.headerTagText}>{sectionData.section.title.substr(0, 1)}</Text> </View> <Text style={styles.headerText}>{sectionData.section.title}</Text> </View> ); } ...
Tabela 3.89 – Implementando a propriedade renderSectionHeader do componente SectionList
O método _renderSectionHeader representa o título das seções. Não
codificamos nada que ainda não estudamos no trecho de código apresentado na Tabela
3.89 – Implementando a propriedade renderSectionHeader do componente SectionList.
96
A única observação é que no estilo hearderConteiner a propriedade flexDirections é
igual a row, ou seja, os componentes filhos serão organizados um ao lado do outro, na
horizontal.
JavaScript ... _renderItem(itemData) { return ( <TouchableOpacity> <View style={styles.itemConteiner}> <Text style={styles.itemTextTitle}>{itemData.item.title}</Text> <Text>{itemData.item.resume}</Text> </View> </TouchableOpacity> ); } ...
Tabela 3.90 – Implementando a propriedade renderItem do componente SectionList
O método _renderItem representa cada Task que será listada em nossa tela.
Também não temos nenhuma novidade que ainda não estudamos no trecho de código
apresentado na Tabela 3.90 – Implementando a propriedade renderItem do componente
SectionList.
JavaScript ... render() { return ( <SectionList renderSectionHeader={(section) => this.renderSectionHeader(section)} sections={[ { data: this.state.tasks.filter((data) => { return data.priority && !data.isDone }), key: "hightPriority", title: 'Hight Priority' }, { data: this.state.tasks.filter((data) => { return !data.priority && !data.isDone }), key: "lowPriority", title: 'Low Priority' }, ]} renderItem={(data) => this.renderItem(data)} /> ); } ...
Tabela 3.91 – Método render do componente TasListView
Nosso último método é o render. Neste método usamos o componente
SectionList que é o responsável em listar nossas Tasks. Codificamos três pontos
importantes no trecho de código acima. O primeiro ponto que codificamos é a
97
propriedade renderSectionHeader do componente SectionList onde definimos como
será a apresentação do título das seções. A propriedade renderSectionHeader recebe
como parâmetro o método _renderSectionHeader. O segundo ponto que codificamos é
a propriedade sections que recebe como parâmetro um array. Esse array é definido
através de um filtro sobre as Tasks com alta prioridade e baixa prioridade. O último ponto
que codificamos é a propriedade renderItem onde definimos como será a apresentação
de cada Task. A propriedade renderItem recebe como parâmetro o método _renderItem.
Vamos aproveitar e deixar o esquema de import/export dos componentes.
Mesmo esquema que fizemos com as nossas páginas.
4. Vamos criar o arquivo Components.js na pasta src/components e
importar e exportar nosso componente TaskListView;
JavaScript import TaskListView from './TaskListView'; export { TaskListView }
Tabela 3.92 – Esquema de import/export de componentes
Agora precisamos recuperar as tarefas do Firebase.
5. Vamos abrir o arquivo FirebaseApi.js localizado dentro da pasta
src/services e criar o método readTasksFromFirebaseAsync;
JavaScript export const readTasksFromFirebaseAsync = async (listener) => { const user = await currentFirebaseUser(); var tasksReference = firebase .database() .ref(user.uid) .child('tasks'); tasksReference .on('value', (snapshot) => { var tasks = []; snapshot.forEach(function (element) { var task = element.val(); task.key = element.key; tasks.push(task); }); listener(tasks); }); }
Tabela 3.93 – Método para ler as Tasks no Firebase
98
O método readTasksFromFirebaseAsync recebe um método como
parâmetro. O Firebase tem a inteligência de observar qualquer alteração que houver nas
Tasks e notificar o método passado como parâmetro. Essa notificação é realizada
através do método tasksReference.on(...).
6. O próximo passo é abrir nossa classe ToDoTasks.js, definir um state para
guardar a referência das Tasks e implementar os métodos para realizar a
leitura das Tasks;
JavaScript ... import { readTasksFromFirebaseAsync } from '../services/FirebaseApi'; ... export default class ToDoTasks extends Component { ... state = { tasks: [] } ... componentDidMount() { readTasksFromFirebaseAsync(this._fetchTasks.bind(this)); } _fetchTasks(tasks) { const tasksToDo = tasks.filter(t => !t.isDone); this.setState({ tasks: tasksToDo }); } } ...
Tabela 3.94 – Lendo as tarefas no método componentDidMount
O único ponto de destaque no trecho do código da Tabela 3.94 – Lendo as
tarefas no método componentDidMount é o método _fetchTasks que, ao receber as
tarefas repassadas pelo método readTaskFromFirebaseAsync, realiza um filtro
somente das tarefas que não estão concluídas. Esse procedimento se dá através do
código tasks.filter(t => !t.isDone).
7. Último passo é ajustar a página ToDoTasks, adicionar o componente
TaskListView e enviar a referência do state tasks. Vamos importar o
componente TaskListView e ajustar o método render.
JavaScript
99
... import { TaskListView } from '../components/Components'; import { readTaskFromFirebaseAsync } from '../services/FirebaseApi'; ... export default class ToDoTasks extends Component { ... render() { return ( <View style={styles.container}> <TaskListView tasks={this.state.tasks} /> <TouchableOpacity style={styles.floatButton} onPress={() => this._goToTask()}> <Image source={imgPlus} style={styles.img} /> </TouchableOpacity> </View> ); } ...
Tabela 3.95 – Ajustando o método render da página ToDoTasks
Pronto! Agora é só testarmos.
Neste subcapítulo alteramos três classes. Vamos ver o resultado das classes
através da Tabela 3.96 – Método readTasksFromFirebaseAsync para ler as Tasks,
Tabela 3.97 - Componente TaskListView e Tabela 3.98 – Página ToDoTasks.
JavaScript import firebase from 'firebase'; const config = { apiKey: "AIzaSyDtVC3VQ1Z8XzQYDkWwnTOC_NFo8ny5c90", authDomain: "todomanager-5444a.firebaseapp.com", databaseURL: "https://todomanager-5444a.firebaseio.com", projectId: "todomanager-5444a", storageBucket: "todomanager-5444a.appspot.com", messagingSenderId: "254572727152" }; export const initializeFirebaseApi = () => firebase.initializeApp(config); export const createUserOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .createUserWithEmailAndPassword(email, password); return user; } export const signInOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .signInWithEmailAndPassword(email, password); return user; }
100
export const currentFirebaseUser = () => { return new Promise((resolve, reject) => { var unsubscribe = null; unsubscribe = firebase .auth() .onAuthStateChanged((user) => { resolve(user); }, (error) => { reject(error); }, () => { unsubscribe(); }); }); } export const writeTaskOnFirebaseAsync = async (task) => { const user = await currentFirebaseUser(); var tasksReference = firebase .database() .ref(user.uid); const key = tasksReference .child('tasks') .push() .key; return await tasksReference .child(`tasks/${key}`) .update(task); } export const readTaskFromFirebaseAsync = async (listener) => { const user = await currentFirebaseUser(); var tasksReference = firebase .database() .ref(user.uid) .child('tasks'); tasksReference .on('value', (snapshot) => { var tasks = []; snapshot.forEach(function (element) { var task = element.val(); task.key = element.key; tasks.push(task); }); listener(tasks); }); }
Tabela 3.96 – Método readTasksFromFirebaseAsync para ler as Tasks
JavaScript import React, { Component } from 'react'; import { View, SectionList, Text, TouchableOpacity, StyleSheet } from 'react-native'; export default class TaskListView extends Component { _renderSectionHeader(sectionData) {
101
return ( <View style={styles.headerConteiner}> <View style={styles.headerTagConteiner}> <Text style={styles.headerTagText}>{sectionData.section.title.substr(0, 1)}</Text> </View> <Text style={styles.headerText}>{sectionData.section.title}</Text> </View> ); } _renderItem(itemData) { return ( <TouchableOpacity> <View style={styles.itemConteiner}> <Text style={styles.itemTextTitle}>{itemData.item.title}</Text> <Text>{itemData.item.resume}</Text> </View> </TouchableOpacity> ); } render() { return ( <SectionList renderSectionHeader={(section) => this._renderSectionHeader(section)} sections={[ { data: this.props.tasks.filter((data) => { return data.priority }), key: "hightPriority", title: 'Hight Priority' }, { data: this.props.tasks.filter((data) => { return !data.priority }), key: "lowPriority", title: 'Low Priority' }, ]} renderItem={(data) => this._renderItem(data)} /> ); } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', paddingLeft: 10, paddingRight: 10 }, headerConteiner: { flex: 1, flexDirection: 'row', alignItems: 'center', backgroundColor: 'silver', borderRadius: 25, marginTop: 10 }, headerTagConteiner: { backgroundColor: 'gray', height: 35,
102
width: 35, alignItems: 'center', justifyContent: 'center', borderRadius: 25 }, headerTagText: { color: '#FFF', fontSize: 22 }, headerText: { fontSize: 16, marginLeft: 10 }, itemConteiner: { flex: 1, flexDirection: 'column', backgroundColor: '#F3F2F0', marginTop: 5, padding: 10, height: 75 }, itemTextTitle: { fontSize: 22 } });
Tabela 3.97 - Componente TaskListView
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View, TouchableOpacity } from 'react-native'; import { TaskListView } from '../components/Components'; import { readTaskFromFirebaseAsync } from '../services/FirebaseApi'; const imgCheckList = require('../assets/checklist.png'); const imgPlus = require('../assets/plus_64.png'); export default class ToDoTasks extends Component { static navigationOptions = { tabBarLabel: 'To Do', tabBarIcon: ({ tintColor }) => ( <Image source={imgCheckList} style={[styles.icon, { tintColor }]} /> ) } state = { tasks: [] } render() { return ( <View style={styles.container}> <TaskListView tasks={this.state.tasks} /> <TouchableOpacity style={styles.floatButton} onPress={() => this._goToTask()}> <Image source={imgPlus} style={styles.img} /> </TouchableOpacity> </View> ); }
103
_goToTask() { this.props.navigation.navigate('pageTask'); } componentDidMount() { readTaskFromFirebaseAsync(this._fetchTasks.bind(this)); } _fetchTasks(tasks) { const tasksToDo = tasks.filter(t => !t.isDone); this.setState({ tasks: tasksToDo }); } } const styles = StyleSheet.create({ container: { flex: 1, paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 }, floatButton: { position: 'absolute', right: 20, bottom: 20 } });
Tabela 3.98 – Página ToDoTasks
3.15 AJUSTANDO A LISTAGEM DONE
No subcapítulo anterior criamos nosso componente de listagem das tarefas e
ajustamos a listagem To Do. Neste subcapítulo vamos praticamente replicar o que
fizemos na classe ToDoTasks para a classe DoneTasks.
1. Vamos abrir nossa classe DoneTasks.js, definir um state para guardar a
referência das Tasks e implementar os métodos para realizar a leitura das
Tasks;
JavaScript ... import { TaskListView } from '../components/Components'; import { readTasksFromFirebaseAsync } from '../services/FirebaseApi'; ... export default class DoneTasks extends Component {
104
... state = { tasks: [] } render() { return ( <View style={styles.container} > <TaskListView tasks={this.state.tasks} /> </View> ); } componentDidMount() { readTasksFromFirebaseAsync(this._fetchTasks.bind(this)); } _fetchTasks(tasks) { const tasksToDo = tasks.filter(t => t.isDone); this.setState({ tasks: tasksToDo }); } } ...
Tabela 3.99 – Lendo as tarefas no método componentDidMount
Assim como fizemos na classe ToDoTasks o método _fetchTasks também
realiza um filtro, mas desta vez realiza um filtro somente das tarefas que estão concluídas.
Esse procedimento se dá através do código tasks.filter(t => t.isDone).
Pronto! Agora é só testarmos, mas antes, vamos ver como ficou nossa classe
DoneTasks através da Tabela 3.100 – Página DoneTasks.
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View } from 'react-native'; import { TaskListView } from '../components/Components'; import { readTasksFromFirebaseAsync } from '../services/FirebaseApi'; const imgDone = require('../assets/done.png'); export default class DoneTasks extends Component { static navigationOptions = { tabBarLabel: 'Done', tabBarIcon: ({ tintColor }) => ( <Image source={imgDone} style={[styles.icon, { tintColor }]} /> ) } state = { tasks: [] }
105
render() { return ( <View style={styles.container} > <TaskListView tasks={this.state.tasks} /> </View> ); } componentDidMount() { readTasksFromFirebaseAsync(this._fetchTasks.bind(this)); } _fetchTasks(tasks) { const tasksToDo = tasks.filter(t => t.isDone); this.setState({ tasks: tasksToDo }); } } const styles = StyleSheet.create({ container: { flex: 1, paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 } });
Tabela 3.100 – Página DoneTasks
3.16 ATUALIZANDO UMA TAREFA
Falta muito pouco para deixarmos nossa aplicação funcional. Neste
subcapítulo vamos alterar a tela que incluir uma nova tarefa para também alterar uma
tarefa existente.
1. Vamos abrir o arquivo FirebaseApi.js localizado dentro da pasta
src/services/ e alterar o método writeTaskOnFirebase;
JavaScript export const writeTaskOnFirebaseAsync = async (task) => { const user = await currentFirebaseUser(); var tasksReference = firebase .database() .ref(user.uid); const key = task.key ? task.key : tasksReference .child('tasks')
106
.push() .key; return await tasksReference .child(`tasks/${key}`) .update(task); }
Tabela 3.101 – Método para alterar Task no Firebase
O método writeTaskOnFirebase recebe o parâmetro task – que é
representado por um json – e repassa esse parâmetro invocando o método update da
biblioteca do Firebase, mas desta vez, fazemos uma verificação se o objeto task possui
a propriedade key definida. Se a propriedade key estiver definida sabemos que se trata
de uma alteração e usaremos o valor desta propriedade como referência. Caso contrário,
solicitaremos uma nova referência para o Firebase.
2. Dando continuidade em nossa codificação, vamos abrir o arquivo
ToDoTask.js e DoneTasks localizados na pasta src/screens/. Vamos
localizar o componente TaskListView e passar a referência do navegador
como parâmetro;
JavaScript ... render() { return ( ... <TaskListView tasks={this.state.tasks} navigation={this.props.navigation} /> ... ); } ...
Tabela 3.102 – Passando a referência do navegador
3. Agora, vamos abrir o componente TaskListView e implementar o clique
sobre a tarefa;
JavaScript ... _renderItem(itemData) { return ( <TouchableOpacity onPress={() => this._onClickTask(itemData.item)}> <View style={styles.itemConteiner}> <Text style={styles.itemTextTitle}>{itemData.item.title}</Text> <Text>{itemData.item.resume}</Text> </View> </TouchableOpacity> ); }
107
_onClickTask(task) { const { navigate } = this.props.navigation; navigate('pageTask', { task }); } ...
Tabela 3.103 – Definindo a classe Task
Fizemos duas alterações importantes em nosso componente TaskListView. A primeira alteração foi criar o método _onClickTask que navega para a página Task
passando como parâmetro a tarefa do item que clicamos. A segunda alteração foi
executar o método _onClickTask quando clicamos na tarefa.
4. Por último, vamos ajustar nossa tela Task para recuperar a tarefa passada
como parâmetro;
JavaScript ... export default class Task extends Component { ... state = { key: '', title: '', resume: '', priority: true, isDone: false }; constructor(props) { super(props); try { const task = this.props.navigation.state.params.task; this.state = { key: task.key, title: task.title, resume: task.resume, priority: task.priority, isDone: task.isDone }; } catch (error) { } } ... async _saveTaskAsync() { var task = { key: this.state.key, title: this.state.title, resume: this.state.resume, priority: this.state.priority, isDone: this.state.isDone };
108
try { await writeTaskOnFirebaseAsync(task); this.props.navigation.goBack(); } catch (error) { Alert.alert("Erro Saving", error.message); } } } ...
Tabela 3.104 – Recuperando uma Task passada como parâmetro
No construtor da página recuperamos a objeto Task passado como parâmetro
e no método _saveTaskAsync repassamos para o Firebase a Task recebida.
Neste subcapítulo alteramos cinco arquivos. Vamos ver o resultado desse
trabalho através dos trechos de códigos apresentados na Tabela 3.105 – Classe de
serviço FirebaseApi, Tabela 3.106 – Página ToDoTask, Tabela 3.107 – Página
DoneTasks, Tabela 3.108 – Componente TaskListView e Tabela 3.109 – Págna Task.
JavaScript import firebase from 'firebase'; const config = { apiKey: "AIzaSyDtVC3VQ1Z8XzQYDkWwnTOC_NFo8ny5c90", authDomain: "todomanager-5444a.firebaseapp.com", databaseURL: "https://todomanager-5444a.firebaseio.com", projectId: "todomanager-5444a", storageBucket: "todomanager-5444a.appspot.com", messagingSenderId: "254572727152" }; export const initializeFirebaseApi = () => firebase.initializeApp(config); export const createUserOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .createUserWithEmailAndPassword(email, password); return user; } export const signInOnFirebaseAsync = async (email, password) => { const user = await firebase .auth() .signInWithEmailAndPassword(email, password); return user; } export const currentFirebaseUser = () => { return new Promise((resolve, reject) => { var unsubscribe = null; unsubscribe = firebase
109
.auth() .onAuthStateChanged((user) => { resolve(user); }, (error) => { reject(error); }, () => { unsubscribe(); }); }); } export const writeTaskOnFirebaseAsync = async (task) => { const user = await currentFirebaseUser(); var tasksReference = firebase .database() .ref(user.uid); const key = task.key ? task.key : tasksReference .child('tasks') .push() .key; return await tasksReference .child(`tasks/${key}`) .update(task); } export const readTasksFromFirebaseAsync = async (listener) => { const user = await currentFirebaseUser(); var tasksReference = firebase .database() .ref(user.uid) .child('tasks'); tasksReference .on('value', (snapshot) => { var tasks = []; snapshot.forEach(function (element) { var task = element.val(); task.key = element.key; tasks.push(task); }); listener(tasks); }); }
Tabela 3.105 – Classe de serviço FirebaseApi
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View, TouchableOpacity } from 'react-native'; import { TaskListView } from '../components/Components'; import { readTasksFromFirebaseAsync } from '../services/FirebaseApi'; const imgCheckList = require('../assets/checklist.png'); const imgPlus = require('../assets/plus_64.png'); export default class ToDoTasks extends Component {
110
static navigationOptions = { tabBarLabel: 'To Do', tabBarIcon: ({ tintColor }) => ( <Image source={imgCheckList} style={[styles.icon, { tintColor }]} /> ) } state = { tasks: [] } render() { return ( <View style={styles.container}> <TaskListView tasks={this.state.tasks} navigation={this.props.navigation} /> <TouchableOpacity style={styles.floatButton} onPress={() => this._goToTask()}> <Image source={imgPlus} style={styles.img} /> </TouchableOpacity> </View> ); } _goToTask() { this.props.navigation.navigate('pageTask'); } componentDidMount() { readTasksFromFirebaseAsync(this._fetchTasks.bind(this)); } _fetchTasks(tasks) { const tasksToDo = tasks.filter(t => !t.isDone); this.setState({ tasks: tasksToDo }); } } const styles = StyleSheet.create({ container: { flex: 1, paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 }, floatButton: { position: 'absolute', right: 20, bottom: 20 } });
Tabela 3.106 – Página ToDoTask
111
JavaScript import React, { Component } from 'react'; import { Image, StyleSheet, View } from 'react-native'; import { TaskListView } from '../components/Components'; import { readTasksFromFirebaseAsync } from '../services/FirebaseApi'; const imgDone = require('../assets/done.png'); export default class DoneTasks extends Component { static navigationOptions = { tabBarLabel: 'Done', tabBarIcon: ({ tintColor }) => ( <Image source={imgDone} style={[styles.icon, { tintColor }]} /> ) } state = { tasks: [] } render() { return ( <View style={styles.container} > <TaskListView tasks={this.state.tasks} navigation={this.props.navigation} /> </View> ); } componentDidMount() { readTasksFromFirebaseAsync(this._fetchTasks.bind(this)); } _fetchTasks(tasks) { const tasksToDo = tasks.filter(t => t.isDone); this.setState({ tasks: tasksToDo }); } } const styles = StyleSheet.create({ container: { flex: 1, paddingLeft: 10, paddingRight: 10 }, icon: { width: 26, height: 26 }, img: { width: 50, height: 50 } });
Tabela 3.107 – Página DoneTasks
JavaScript import React, { Component } from 'react';
112
import { View, SectionList, Text, TouchableOpacity, StyleSheet } from 'react-native'; export default class TaskListView extends Component { _renderSectionHeader(sectionData) { return ( <View style={styles.headerConteiner}> <View style={styles.headerTagConteiner}> <Text style={styles.headerTagText}>{sectionData.section.title.substr(0, 1)}</Text> </View> <Text style={styles.headerText}>{sectionData.section.title}</Text> </View> ); } _renderItem(itemData) { return ( <TouchableOpacity onPress={() => this._onClickTask(itemData.item)}> <View style={styles.itemConteiner}> <Text style={styles.itemTextTitle}>{itemData.item.title}</Text> <Text>{itemData.item.resume}</Text> </View> </TouchableOpacity> ); } _onClickTask(task) { const { navigate } = this.props.navigation; navigate('pageTask', { task }); } render() { return ( <SectionList renderSectionHeader={(section) => this._renderSectionHeader(section)} sections={[ { data: this.props.tasks.filter((data) => { return data.priority }), key: "hightPriority", title: 'Hight Priority' }, { data: this.props.tasks.filter((data) => { return !data.priority }), key: "lowPriority", title: 'Low Priority' }, ]} renderItem={(data) => this._renderItem(data)} /> ); } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', paddingLeft: 10, paddingRight: 10 },
113
headerConteiner: { flex: 1, flexDirection: 'row', alignItems: 'center', backgroundColor: 'silver', borderRadius: 25, marginTop: 10 }, headerTagConteiner: { backgroundColor: 'gray', height: 35, width: 35, alignItems: 'center', justifyContent: 'center', borderRadius: 25 }, headerTagText: { color: '#FFF', fontSize: 22 }, headerText: { fontSize: 16, marginLeft: 10 }, itemConteiner: { flex: 1, flexDirection: 'column', backgroundColor: '#F3F2F0', marginTop: 5, padding: 10, height: 75 }, itemTextTitle: { fontSize: 22 } });
Tabela 3.108 – Componente TaskListView
JavaScript import React, { Component } from 'react'; import { View, TextInput, Switch, Text, Button, StyleSheet } from 'react-native'; import { writeTaskOnFirebaseAsync } from '../services/FirebaseApi'; export default class Task extends Component { static navigationOptions = { title: 'Task' } state = { key: '', title: '', resume: '', priority: true, isDone: false }; constructor(props) { super(props); try { const task = this.props.navigation.state.params.task;
114
this.state = { key: task.key, title: task.title, resume: task.resume, priority: task.priority, isDone: task.isDone }; } catch (error) { } } render() { return ( <View style={styles.container}> <TextInput style={styles.input} placeholder='Title' value={this.state.title} onChangeText={(value) => this.setState({ title: value })} /> <TextInput style={[styles.input, styles.multilineInput]} placeholder='Resume' multiline={true} numberOfLines={4} value={this.state.resume} onChangeText={(value) => this.setState({ resume: value })} /> <View style={styles.switchContainer}> <Switch value={this.state.priority} onValueChange={(value) => this.setState({ priority: value })} value={this.state.priority} /> <Text style={styles.switchText}>Hight Priority</Text> </View> <View style={styles.switchContainer}> <Switch value={this.state.isDone} onValueChange={(value) => this.setState({ isDone: value })} value={this.state.isDone} /> <Text style={styles.switchText}>Is Done?</Text> </View> <Button style={styles.button} title='Save' onPress={async () => this._saveTaskAsync()} /> </View> ); } async _saveTaskAsync() { var task = { key: this.state.key, title: this.state.title, resume: this.state.resume, priority: this.state.priority, isDone: this.state.isDone }; try { await writeTaskOnFirebaseAsync(task); this.props.navigation.goBack(); } catch (error) { Alert.alert("Erro Saving", error.message); } } }
115
const styles = StyleSheet.create({ container: { flex: 1, padding: 20, }, input: { marginBottom: 20 }, multilineInput: { height: 100 }, switchContainer: { flexDirection: 'row', alignItems: 'center', paddingBottom: 20 }, switchText: { marginLeft: 10, color: 'black', fontSize: 18 } });
Tabela 3.109 – Págna Task
4 REFERÊNCIAS BIBLIOGRÁFICAS
NOVICK, Vladimir. React Native – Building Mobile Apps with JavaScript. Editora Packt
Publishing Limited. Primeira Edição, Agosto de 2017, 434 páginas.
KHO, Richard. React Native By Example. Editora Packt Publishing Limited. Primeira
Edição, Abril de 2017, 414 páginas.
SANT ANA, Jorge e DAMASCENO, Jamilton. Desenvolvedor Multiplataforma
Android/iOS com React Native e Redux. Disponível em
<https://www.udemy.com/desenvolvedor-multiplataforma-androidios-com-react-e-
redux/learn/v4/t/lecture/6275548?start=0>. Acesso em 20 de Setembro de 2017.
React Navigation Docs. Hello Mobile Navigation. Disponível em
<https://reactnavigation.org/docs/intro/>. Acesso em 9 de Outubro de 2017.