Upload de Imagens e uso da Câmera no React Native

Série Clone AirBnB com AdonisJS, React Native e ReactJS [Parte 6]

Upload de Imagens e uso da Câmera no React Native

Esse post é a sexta parte da série de posts “Clone AirBnB com AdonisJS, React Native e ReactJS” onde iremos construir do zero uma aplicação web com ReactJS e também uma aplicação mobile com React Native com dados servidos através de uma API REST feita com NodeJS utilizando o framework AdonisJS.

Nesse ponto já temos a aplicação criada e configurada para se comunicar com a API desenvolvida no começo dessa série, e com isso já conseguimos implementar o processo de autenticação e cadastro de usuário, nessa parte iremos instalar e configurar a Câmera para podermos além de selecionar as imagens do dispositivo, tirar fotos para o cadastro do imóvel, e por fim enviar essas imagens para serem cadastradas no banco de dados da API.

Iniciando a Aplicação

Caso você tenha entrado direto nessa parte, você pode acessar esse link e baixar o projeto com todas as configurações do post anterior, apesar de eu recomendar pessoalmente que você leia as duas partes anteriores para entender o fluxo da aplicação.

Ou se você já viu as partes anteriores pode continuar para a próxima sessão onde iremos começar a instalação da lib de Câmera.

Instalando e Configurando o react-native-camera

Para não deixar o post muito extenso explicando o passo a passo da instalação e configuração do react-native-camera, você pode correr no Blog da Rocketseat e ler esse post sobre a instalação e configuração da Câmera.

Caso você tenha algum problema na instalação ou não tenha conseguido fazer todas as configurações acesse esse link e baixe a estrutura configurada para dar continuidade.

Criando Primeira Tela do Cadastro de Imóveis

Para começar o cadastro a primeira coisa que vamos fazer é criar um botão sobre o mapa para que quando clicado comece o processo de cadastro do imóvel, e como nesse post criaremos vários novos estilos, vamos fazer uma pequena mudança no arquivo de estilos para que fique mais organizado, primeiramente retire a instrução export do final do arquivo src/pages/main/styles.js em seguida, adicione um export antes de todas as const’s, e feito isso vamos criar os seguintes componentes:

Obs.: Não se esqueça de importá-los no src/pages/main/index.js.

Em seguida vamos deixar o state preparado para toda a parte de cadastro de um novo imóvel, seu código final vai ficar:

Temos uma variável de controle para os botões de Novo Imóvel e Selecionar Localização, 2 variáveis de controle para os modais que implementaremos logo na sequência (cameraModalOpened e dataModalOpened) e um objeto que irá guardar todas as informações referentes ao imóvel (realtyData).

A primeira coisa que devemos fazer no processo de cadastro de um imóvel é selecionar qual a localização do mesmo, e para isso iremos reaproveitar o botão de Novo Imóvel para fazer uma renderização condicional, quando a variável newRealty do State for false será mostrado o botão Novo Imóvel, quando ela for true serão mostrados os botões para Selecionar Localização e Cancelar, como na imagem abaixo:

exemplo-tela-inicial-com-rederizacao-condicional

Para que os botões funcionem corretamente vamos criar uma função chamada renderConditionalsButtons entre o componentDidMount e o método render() e seu conteúdo será o seguinte:

Nessa parte usamos todos os componentes criados para os botões, mas ainda faltou criarmos as funções de callback dos botões e também chamar essa função no método render(), segue abaixo o escopo dessas 2 funções que devem ser criadas logo abaixo da função renderConditionalsButtons:

A função handleNewRealtyPress apenas muda o valor da variável newRealty de true para false e vice-versa, já a função handleGetPositionPress utiliza um método disponibilizado pelo Mapbox que permite obter o ponto central do mapa, em seguida usamos esse ponto para adicionar as coordenadas selecionadas no objeto location dentro do objeto realtyData, mas para que essa função funcione corretamente temos que adicionar a propriedade ref no componente de mapa, para isso basta adicionar as linhas abaixo no MapboxGL.MapView:

ref={map => {
  this.map = map;
}}

E por último vamos dentro do método render, dentro do Container, logo após o fechamento do componente MapboxGL.MapView e adicionar a seguinte instrução: { this.renderConditionalsButtons() }

Agora quando você rodar a aplicação os botões devem estar funcionando corretamente, mas ainda falta um detalhe que pode atrapalhar na experiência do usuário, no momento de selecionar a localização não tem em lugar algum apontando onde fica exatamente o meio, por isso iremos criar um Marker que irá indicar exatamente onde é o meio a ser selecionado, para isso acesse esse link para obter a imagem do marker, que está dentro da pasta src/images.

Antes mesmo de criarmos a função para inserir o Marker no mapa vamos novamente para o src/pages/main/styles.js e criar mais um componente, ficando assim:

E para que não haja erro você deve também adicionar a importação para o Dimensions no começo desse arquivo, ficando assim:

import { Dimensions } from 'react-native';

A função acima irá criar um componente que irá sobrepor o mapa e sua parte inferior, ou seja, a ponta do Marker será posicionada exatamente no meio da tela, agora adicionando esse componente no export dos estilos e adicionando no import desses componentes na src/pages/main/index.js podemos criar a função renderMarker, que deve ficar assim:

Seleção de Imóveis com o Marker no Centro

É uma função bem simples que faz uma renderização condicional baseada no valor de 2 variáveis do state, a newRealty e cameraModalOpened, ou seja, esse Marker vai aparecer apenas quando o newRealty for true e o cameraModalOpened for false.

E assim terminamos a primeira parte do cadastro, onde já é possível selecionar a opção de Cadastrar um novo imóvel e selecionar a localização do mesmo no mapa, retornando as coordenadas para o state.

Agora bora para a próxima parte? (aleluia \o/)

Criação do Modal para Captura de Imagens do Imóvel

Uma vez seleciona a localização do imóvel vamos para a próxima etapa, que é permitir que o usuário tire fotos do imóvel, para posteriormente cadastrá-las junto com os demais dados, e para isso a primeira coisa que vamos fazer é criar o estilo dos componentes, como a lista para o Modal ficou um pouco extensa, deixei nesse link as constantes que vemos adicionar no src/pages/main/styles.js.

No src/pages/main/index.js o import deve ficar assim:

Agora vamos à criação do Modal, e para que não haja erros, primeiro vamos adicionar algumas importações no início do arquivo index.js, sendo elas:

import { StatusBar, Modal } from 'react-native'; // Adicionado o Modal
import { RNCamera } from 'react-native-camera';

Feito isso vamos criar a função responsável por renderizar o componente Modal na página, sobre o mapa,  a função criada vai se chamar renderCameraModal, ela pode ser adicionada logo antes do método render() e seu escopo ficará assim:

Essa função é a responsável por criar um Modal na tela, cuja exibição é controlada pela variável cameraModalOpened do state do componente, dentro do Modal é criada uma estrutura que contém na parte superior a câmera com um botão para capturar a imagem, na parte inferior 2 botões, um para continuar para a próxima tela e um para cancelar, e no meio disso quando houverem imagens capturadas pela câmera irá aparecer uma lista com miniaturas das imagens capturadas.

Modal para captura de imagens

Obs.: Não se preocupe, a câmera só ficou assim por estar em um emulador.

Para que essa parte funcione corretamente é necessário que sejam criadas algumas funções que são chamadas dentro da função renderCameraModal, são elas: handleTakePicture, renderImagesList, handleCameraModalClose e handleDataModalClose, segue abaixo o código delas:

Agora uma breve explicação do funcionamento de cada uma:

  • handleCameraModalClose e handleDataModalClose: Ambas tem a função apenas de fazer um toggle (inversão) do valor do state das variáveis cameraModalOpened e dataModalOpened;
  • handleTakePicture: Essa função captura a imagem da câmera e adiciona no array images dentro de realtyData no state;
  • renderImagesList: Essa função não renderiza nada caso não haja imagens capturadas, mas caso haja faz um map no array de imagens e lista cada um em um <Image> dentro de um <ScrollView> horizontal.

E assim acaba mais uma parte da sequência de cadastro, a próxima e última parte é inserir as demais informações e fazer o cadastro na API.

Modal de Câmera com imagens capturadas

Caso você tenha alguma dúvida ou problema pode conferir o código da aplicação nesse link.

Criação da Tela de Informações do Imóvel e efetivando o cadastro na API

Nessa última parte vamos criar outro Modal para listar e preencher os últimos dados do imóvel, antes de realizar o cadastro na API, por isso para iniciar vamos criar os estilos, mas como ficou um pouco extenso deixei disponível nesse link, basta copiar todo o conteúdo e colar no seu arquivo src/pages/main/styles.js.

Feito isso adicione todos os componentes no import do arquivo src/pages/main/index.js.

Agora sim podemos criar a função para renderizar o Modal, para isso vamos criar uma função renderDataModal, seu código vai ficar assim:

Como vocês perceberam ele ficou um pouco longo, mas isso porque temos mais componentes do que lógica nesse Modal, essa função é bem semelhante à do Modal de câmera onde temos um Modal com um Container, dentro dele temos os componentes que serão renderizados, e nesse Modal temos no começo um Mapa como na Tela Principal, a diferença é que dentro dele temos um Marker para a localização selecionada, depois disso temos a lista de imagens como na tela anterior, no final temos novamente 2 botões, a diferença principal da outra tela é a presença do Form com 3 campos para preenchimento de informações, e é nesse form que vamos utilizar as variáveis name, address e price do objeto realtyData do State.

Modal de Dados do Imóvel

E para finalizar vamos criar as funções usadas pelo Modal para que não haja erro, temos que criar as funções handleInputChange e saveRealty, primeiro vou postar o código da função que trata a mudança dos Inputs para depois focarmos na função de cadastro do Imóvel.

As funções acima tem o objetivo apenas de mudar a variável referente ao campo no state.

E agora sim, a cereja do bolo (aeeeee 😀), a função que irá efetivar o cadastro do imóvel na API, a saveRealty, primeiro vou postar seu código para depois abordar a lógica usada, e como o código ficou um pouco extenso resolvi deixar disponível nesse link basta copiar esse código e colar no seu componente junto com as demais funções, mas não se preocupe, como eu já disse, vou quebrá-la em partes e explicar a lógica de cada trecho.

O primeiro trecho que encontramos é o seguinte:

Esse trecho é uma desestruturação do state para obtermos todos os dados referentes ao imóvel, o próximo trecho é:

Esse trecho estamos enviando à API uma requisição POST com os dados do imóvel para que haja o cadastro efetivo do imóvel e nos seja retornado um objeto com o id do imóvel no banco de dados, que iremos usar logo na sequência.

O próximo trecho é o ponto chave dessa função, ele é o responsável por organizar as imagens para serem enviadas corretamente para a API e serem movidas para o servidor e cadastradas no banco de dados, segue o código:

Nesse trecho usamos o FormData que é um recurso que nos permite criar um conjunto de pares chave/valor representando campos de um formulário com seus respectivos valores, e é ele que iremos usar para enviar as imagens, por isso logo abaixo recuperamos o array images do state e usamos um forEach para percorrer cada índice dele, e a cada índice é adicionado no FormData uma posição com o nome image e como valor um objeto contendo o endereço da imagem, o tipo dela e também um nome único.

Agora com as imagens organizadas vamos ao envio da requisição:

Nesse trecho apenas usamos o Axios para enviar uma requisição para a rota /properties/:id/images da API com o FormData previamente formatado.

E por último, antes de chamar as 3 últimas funções para finalizar a parte de cadastro, temos que fazer uma pequena mudança, no vamos tirar o código da requisição que está no componentDidMount e passar para uma função separada, que será usada logo na sequência, ficando dessa maneira:

Agora sim, vamos à chamada das funções no final da saveRealty, ficando assim:

this.getLocation();
this.setState({ newRealty:false });
this.handleDataModalClose();

Elas basicamente atualizam a lista de imóveis no state, depois é voltado o botão de Novo Imóvel na tela do mapa e por último é fechado o Modal de Dados.

Tela Inicial com novo Imóvel

Com tudo isso feito sua aplicação deve funcionar corretamente permitindo todo o processo de cadastro de um novo imóvel, você também pode conferir o código completo nesse link para comparar com o seu ou realizar testes caso tenha alguma dúvida ou problema 😉

Considerações Finais

Como você percebeu essa parte ficou um pouco mais extensa que as anteriores, mas isso devido à complexidade do processo de cadastro e do número de etapas necessárias.

A aplicação nesse ponto está quase pronta, falta apenas a listagem dos detalhes dos imóveis, mas isso é para a próxima parte, afinal já temos uma aplicação que faz Autenticação usando JWT, uso de Mapas, uso de Câmera, Upload de Imagens, dentre outras funcionalidades que já implementamos, percebe o quanto a aplicação está completa e com recursos que no dia a dia são um diferencial em uma aplicação!?

Apenas reforçando, você pode conferir o código final nesse link 😉

Espero que tenha gostado, deixe seu comentário/dúvida/crítica/sugestão que é muito importante para nós 😀

Até a próxima parte \o/

AdonisJS + ReactJS + React Native
A seguir:

Iniciando com React Native: Navegação e Autenticação com JWT

Iniciando com React Native: Navegação e Autenticação com JWT