Por que eu devo ler este artigo:O React Native é um novo módulo lançado pelo Facebook para o desenvolvimento de aplicativos móveis multiplataforma. Neste artigo, trataremos de explorar as principais features dessa biblioteca através da criação de um aplicativo de busca de restaurantes focado para a plataforma iOS.

Através disso, você estará apto a entender como o React se comunica com os dispositivos, o que é o JSX, como implementar controles de navegação, assim como usar os principais widgets que a tecnologia disponibiliza e tudo usando somente JavaScript, CSS e HTML5. Ao final, veremos como conectar o aplicativo diretamente a um serviço de Web Service com consumo de conteúdo JSON, tal qual temos nos aplicativos reais.

Recentemente, na última Conferência React realizada no escritório do Facebook em Menlo Park, foi lançada a biblioteca React Native. Ela constitui um novo módulo introduzido à biblioteca principal, a React JavaScript, que fornece suporte ao desenvolvimento de aplicações móveis usando JavaScript, CSS e HTML5. Na seção Links deste artigo você encontra as referências para ambos os sites de cada uma.


Curso: O que é React?


Por se tratar de uma biblioteca nova, com recursos novos, muita gente se questiona sobre a real utilidade da mesma, e se vale a pena investir em incorporar mais uma tecnologia a seus projetos. Por outro lado, muitos desenvolvedores consideram a biblioteca eficiente e recomendável, principalmente por duas razões:

  • Com o React Native a sua aplicação, bem como toda a lógica interna a ela, é toda escrita e executada em JavaScript, o que te possibilita ter um UI (User Interface) totalmente nativo. Logo, você não tem de assumir nenhum compromisso tipicamente associado ao UI da HTML5.
  • O React introduz uma abordagem nova e altamente funcional para a construção de interfaces de usuário: a interface do usuário é simplesmente expressa como uma nova função do estado atual do aplicativo.

À primeira vista, o código e meios de uso do React Native são muito semelhantes à forma como os fazíamos com o React, com declarações JavaScript relativas às UIs, mas por detrás de tudo as interfaces do mesmo tem suporte em controllers específicos da biblioteca, em vez de em elementos do objeto DOM.

O ponto chave do React Native é que ele pretende trazer principalmente o poder do modelo de programação do React para o desenvolvimento de aplicativos móveis. O maior objetivo dele é implantar o conceito de uma plataforma learn-once run-anywhere (aprenda uma vez e execute em qualquer lugar), isto é, uma vez aprendidos os conceitos do React, os mesmos podem ser transcritos para qualquer linguagem de programação, tal como funciona com a lógica de programação.

Neste artigo trataremos de expor todas as principais particularidades relacionadas ao React Native, através da construção de uma aplicação nativa em iOS para efetuar a busca de restaurantes de uma marca fictícia que criaremos em todo o Brasil (veja na Figura 1 as telas do nosso aplicativo final). O React também lida com a implementação de aplicações para as outras plataformas móveis, como Android, Windows Phone, etc., logo, uma vez aprendidos os conceitos relacionados ao mesmo de uma forma geral, você estará apto a migrar seu código para quaisquer outras plataformas.

Apesar de se tratar de criar aplicativos em iOS, a perspectiva de desenvolvimento bem como os códigos usados para desenvolver com o React é bem diferente dos usados na linguagem Objective-C, mas muito próximos da linguagem Swift. Uma vez familiarizado com o Swift, por exemplo, você será capaz de reaproveitar os conceitos da tecnologia e aplicá-los em algoritmos e técnicas que encorajem transformações e imutabilidade. Entretanto, a forma como você constrói sua UI é praticamente igual à que usamos para desenvolver com o Objective-C: ainda será imperativa e baseada no UIKit.

Telas finais do aplicativo de busca de
restaurantes já pronto
Figura 1. Telas finais do aplicativo de busca de restaurantes já pronto.

Configuração do ambiente

O framework do React Native está disponível para download também no GitHub. Se tiver conhecimentos sobre o Git, poderá efetuar o download do mesmo via clonagem do repositório para o seu ambiente, ou você pode simplesmente efetuar o download como um arquivo zip (pela opção disponibilizada no próprio GitHub) e descompactá-lo normalmente no seu workspace. Outrossim, se não estiver interessado no código fonte do projeto, você pode também usar a interface de linha de comando CLI para criar o projeto React Native, que é a abordagem que usaremos neste artigo.

O React Native faz uso do Node.js para efetuar os builds de código JavaScript. Se você não o tiver instalado ainda em seu Mac, siga os seguintes passos:

1. Abra o seu terminal de comandos e digite o comando: brew install node.

2. Aguarde até que ele termine a execução. Se alguma mensagem de erro for impressa, execute o comando em modo super usuário: sudo brew install node.

3. Pronto, o seu Node.js está instalado a nível de Sistema Operacional.

Caso você já tenha o Node.js instalado, mas está com uma versão antiga, então execute os seguintes comandos para atualizar a versão:


  brew update
  brew upgrade now

Após isso, também precisaremos configurar o Homebrew no seu Mac (se você também não o tiver ainda). O Homebrew é um gerenciador de pacotes para o OS X que incorpora funcionalidades não fornecidas por padrão pelo SO do Mac, como a instalação de pacotes nos próprios diretórios, criação de links simbólicos dos arquivos, além de ser totalmente baseado em Git e Ruby. Para isso, usaremos o terminal de linha de comandos do seu Mac que efetuará os downloads necessários. Execute o seguinte comando no seu terminal:

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Este script irá solicitar a instalação dos pacotes de dependência do Homebrew. Após executá-lo, você verá uma resposta semelhante à ilustrada pela Figura 2.

Resultado da execução do comando de
instalação do Homebrew
Figura 2. Resultado da execução do comando de instalação do Homebrew.

Veja que o comando lista as pastas que o Mac irá instalar na sua máquina, mas antes disso ele pede permissão para que você autorize. Basta pressionar a tecla “return” e o processo de instalação será continuado até o fim. Você saberá que o Homebrew foi instalado com sucesso após receber a mensagem exibida pela Figura 3.

Resultado final da execução do comando
de instalação do Homebrew
Figura 3. Resultado final da execução do comando de instalação do Homebrew.

Perceba também que ao final de todo o processo, o terminal ainda exibe as próximas opções que você pode tomar, como selecionar o comando brew help para analisar quais comandos estão à disposição do usuário. Fique à vontade para explorá-los.

Agora só precisamos instalar (ou verificar se já o estão) três coisas:

O Node.js;

  • O Watchman, que representa um serviço observador (watcher) de arquivos do Facebook. Ele basicamente observa eventuais mudanças em arquivos e o avisa quando elas ocorrerem. Ele também é capaz de executar ações pré-programadas quando alguma mudança se encaixar no teste codificado. No nosso caso, usaremos o Watchman para validar quando o nosso código mudar e, então, efetuar o rebuild automático no mesmo. Isso funciona mais ou menos como no Xcode que faz o rebuild do código sempre que salvamos um arquivo;
  • E por fim, o React Native.

Para verificar se o Node.js foi instalado corretamente, basta digitar o seguinte comando no terminal:

node -v

Se a versão for impressa (v0.12.2, no caso de ser a versão mais recente na data de escrita deste artigo), significa que o Node.js está ok. Caso tenhamos a mensagem “-bash: node: command not found” sendo impressa, significa que você ainda o precisa instalar. O mesmo vale para os demais produtos. Para efetuar as três instalações, basta executar os seguintes comandos, respectivamente:

  • brew install node
  • brew install watchman
  • npm install -g react-native-cli

Todas as instalações findarão com as respectivas mensagens do local onde os pacotes foram instalados. Geralmente eles ficam localizados no diretório “/usr/local/Cellar/”.

Note que o último comando faz uso do gerenciador de pacotes do Node.js, o npm. Verifique se o mesmo está ok executando o comando npm –v (que deverá imprimir 2.7.4).

Para comandos envolvendo o npm, é sempre interessante executar no modo de super usuário, uma vez que estamos fazendo alterações importantes a nível de SO. Se por acaso você receber alguma tela de erro, adicione a cláusula “sudo” antes do comando em questão.

Após isso, teremos a ferramenta CLI instalada de maneira global (mais um motivo para precisarmos executar o comando em modo sudo). O próximo passo agora é navegar até o diretório onde você deseja criar o seu projeto e usar a ferramenta CLI para construir o mesmo, através do seguinte comando:

react-native init BuscadorRestaurantes

Isso irá criar um projeto inicial contendo tudo que você precisará para construir e executar códigos usando o React Native na sua aplicação. Aguarde até que o comando termine todas as configurações e você verá dentro da referida pasta uma estrutura de arquivos e diretórios semelhante à da Figura 4. Vejamos alguns detalhes sobre os recursos mais importantes da mesma:

  • O diretório node_modules contém o framework React Native. Dentro dele você encontrará arquivos de exemplo, as bibliotecas do framework, bem como arquivos de inicialização.
  • O arquivo index.ios.js representa o esqueleto da aplicação criado pela ferramenta CLI.
  • O diretório BuscadorRestauranteTests guardará os arquivos que você precisará para usar como testes.
  • O diretório iOS contém alguns códigos que serão usados para efetuar o bootstrap da sua aplicação.
  • E o mais importante, o arquivo BuscadorRestaurante.xcodeproj contempla o projeto Xcode que usaremos para efetuar toda a implementação principal.
 Estrutura de pastas e arquivos gerada
pelo React Native
Figura 4. Estrutura de pastas e arquivos gerada pelo React Native.

Após isso estaremos aptos a abrir o nosso projeto na IDE do Xcode. Portanto, dê dois cliques no referido arquivo xcodeproj e execute o projeto dentro da ferramenta. Você deverá ver a tela da Figura 5 sendo exibida no emulador do Xcode.

Tela inicial gerada pelo React Native
no nosso aplicativo
Figura 5. Tela inicial gerada pelo React Native no nosso aplicativo.

Você também deve ter notado que uma outra tela de terminal abriu sozinha em forma de pop-up, com o conteúdo da Listagem 1. Ela representa nada mais que o packager (empacotador) do React Native executando sobre o nó do aplicativo. Descobriremos o que ele faz mais à frente. É importante que você não feche nunca essa janela do terminal, apenas a deixe executando no plano de fundo sem se preocupar com ela. Se por acaso você fechá-la sem querer, reexecute o projeto novamente e uma nova será criada.

1. Código gerado pela janela do terminal ao rodar o projeto.


   |  Running packager on port 8081.       
   |  Keep this packager running while developing on any JS         
   |  projects. Feel free to close this tab and run your own      
   |  packager instance if you prefer.                              
   |                                                              
   |     https://github.com/facebook/react-native                 
   |                                                              
   ===============================================================
  Looking for JS files in
     /Users/macbookpro/Documents/BuscadorRestaurante/BuscadorRestaurante 
   
  React packager ready.

AloMundo React Native

Por se tratar de uma tecnologia nova na comunidade, e para que você não comece já pondo a mão na massa sem os conceitos introdutórios da tecnologia, vamos criar um exemplo básico de AloMundo. Para isso, abra o arquivo index.ios.js do nosso projeto em um editor de texto de sua preferência (é aconselhável usar o Sublime Text, pela facilidade de desenvolver código nele) e delete o conteúdo atual do mesmo, adicionando a seguinte linha de código:

‘use strict’;

Isso irá habilitar o Strict Mode (Modo Restrito) do React, o qual adiciona facilidade aos processos de tratamento de exceções fornecendo mensagens mais detalhadas, bem como desabilita algumas funcionalidades do JavaScript que não serão necessárias à nossa realidade. Em resumo, o Strict Mode irá otimizar o JavaScript que usaremos. Em seguida, adicione a seguinte linha de código:

var React = require(‘react-native’);

Esta, por sua vez, carrega o módulo react-native e o assimila ao React. O React Native faz uso do mesmo módulo para carregar tecnologias como o Node.js por intermédio da função require(), que é extremamente semelhante à importação e “linkagem” de bibliotecas no Swift. Após isso, precisamos efetuar mais algumas mudanças ao arquivo de código (Listagem 2).

2. Finalizando o arquivo de JS do AloMundo React Native.


  01 var estilos = React.StyleSheet.create({
  02   text: {
  03     color: "black",
  04     backgroundColor: "white",
  05     fontSize: 30,
  06     margin: 80
  07   }
  08 });
  09 
  10 class BuscadorRestauranteApp extends React.Component {
  11   render() {
  12     return React.createElement(React.Text, {style: estilos.text}, "Alô Mundo!");
  13   }
  14 }
  15 
  16 React.AppRegistry.registerComponent("BuscadorRestaurante", function() { return BuscadorRestauranteApp });

Dentre as mudanças, algumas se destacam, a saber:

  • Da linha 1 a 7, temos a definição da variável estilos que será responsável por conter um estilo simples de CSS a ser utilizado pelo texto que criaremos na tela. Os atributos CSS que estamos usando se equiparam aos disponibilizados nas páginas web, não havendo diferenças no conceito de classe aqui.
  • Na linha 10 declaramos uma classe JavaScript, a BuscadorRestauranteApp, que, por sua vez, herda de React.Component, o bloco básico de construção do React UI. Os Componnets contêm propriedades imutáveis (variáveis de estado imutável), além de expor um método para renderização. Como a nossa aplicação é relativamente simples, só necessitamos de um método de renderização (render, na linha 11).
  • Finalmente, definimos na linha 16 o AppRegistry, que se encarrega de prover o componente raiz como ponto de partida da aplicação.

Agora basta salvar o arquivo com o código em questão e reexecutar a aplicação. Ou você pode optar pelo atalho Command + R, que irá recarregar o projeto no emulador. O resultado deverá ser semelhante ao da Figura 6.

Tela de AloMundo exibida no emulador
Figura 6. Tela de AloMundo exibida no emulador.

Esse é o resultado da nossa execução no emulador, renderizando uma UI nativa, sem fazer uso de nenhum browser. Para ter certeza do que estamos falando, abra o Xcode, selecione o modo de visão “Debug\View Debugging\Capture View Hierarchy” e você verá que nenhuma instância de UIWebView está sendo usada, somente uma View.

Suporte ao JSX

A nossa aplicação atualmente está usando o construtor de React.createElement para construir o UI. Enquanto o nosso código JavaScript é perfeitamente legível na forma que está, uma UI mais complexa com elementos encadeados poderia rapidamente se tornar uma verdadeira bagunça.

Para resolver isso, vamos modificar o trecho do nosso arquivo index.ios.js, especificamente na linha de return do método render(), para o código a seguir:

return <React.Text style={estilos.text}>Alô Mundo (de novo)!</React.Text>;

Isso é o que chamamos de JSX (ou JavaScript Syntax Extension), uma linguagem para demarcar código JavaScript, semelhante à forma como o fazemos com o XML. Ele mixa a sintaxe HTML diretamente com o código JavaScript, e não é de uso obrigatório (você pode usar objetos simples do JavaScript se quiser), mas usaremos no artigo por melhor se adequar aos requisitos de organização do código.

Salve as alterações no arquivo e retorne ao simulador. Mais um vez execute o atalho Cmd + R e você verá a mensagem “Alô Mundo (de novo)!” aparecer no mesmo.

Navegação

O React Native disponibiliza um recurso de navegação padrão baseado em pilhas, que é gerenciado pelo controlador de navegação do UIKit. Vejamos como adicionar tal comportamento.

Dentro do index.ios.js, renomeie a classe atual BuscadorRestauranteApp para AloMundo. Não se preocupe, o texto continuará sendo exibido, mas agora o componente AloMundo não será mais o principal da tela. Em seguida, adicione a classe contida na Listagem 3 logo depois da AloMundo.

3. Código da classe BuscadorRestauranteApp para navegação.


  class BuscadorRestauranteApp extends React.Component {
    render() {
      return (
        <React.NavigatorIOS
          style={estilos.container}
          initialRoute={{
            title: "Buscador de Restaurantes",
            component: AloMundo,
          }}/>
      );
    }
  }
Isso irá construir um controlador de navegação, aplicar um estilo e configurar a rota inicial do componente AloMundo. No mundo de desenvolvimento web, routing (ou roteamento) é uma técnica para definir a estrutura de navegação de uma aplicação, onde as páginas (routers) são mapeadas a URLs. Dentro do mesmo arquivo, não esqueça de adicionar uma nova classe CSS à nossa variável “estilos”:
 
  container: {
    flex: 1
  }

Agora recarregue a página no simulador e o resultado será igual ao da Figura 7.

Tela de AloMundo com navegação
incutida
Figura 7. Tela de AloMundo com navegação incutida.

Página de buscas

Para iniciar essa implementação, adicione um novo arquivo ao projeto chamado PaginaBusca.js e o posicione no mesmo diretório do index.ios.js. Adicione o conteúdo presente na Listagem 4 ao mesmo.

4. Código inicial da página de buscas.


  "use strict";
   
  var React = require("react-native");
  var {
    StyleSheet,
    Text,
    TextInput,
    View,
    TouchableHighlight,
    ActivityIndicatorIOS,
    Image,
    Component
  } = React;

O início da implementação se equipara ao que já fizemos no AloMundo, exceto a sentença de associação que temos no fim. Isso é o que chamamos de “atribuição desestruturada” que nos permite extrair múltiplas propriedades de objetos e atribuí-las a variáveis usando uma simples sentença. Como resultado, podemos referenciar tais propriedades no restante do código diretamente, sem necessidade de usar prefixos como fazíamos antes.

Em seguida, precisamos também adicionar o estilo que a nossa página de buscas terá, tal como fizemos no primeiro exemplo. Para isso, adicione o conteúdo da Listagem 5 ao final do arquivo.

5. Código referente ao estilo da página de buscas.


  var estilos = StyleSheet.create({
    descricao: {
      marginBottom: 18,
      fontSize: 16,
      textAlign: "center",
      color: "grey"
    },
    container: {
      padding: 28,
      marginTop: 64,
      alignItems: "center"
    }
  });

Essas são as propriedades CSS padrão da página. Configurar estilos dessa forma é menos visual do que usar um construtor de interfaces, mas é bem melhor que configurar propriedades da visão uma por uma em um método viewLoad(), por exemplo.

Após isso, vamos configurar o componente de busca de fato, atribuindo o estilo anterior e fazendo uso das propriedades iniciais (Listagem 6).

Listagem 6. Código que cria o componente de buscas de fato.


 class PaginaBusca extends Component {
   render() {
     return (
       <View style={estilos.container}>
         <Text style={estilos.descricao}>
           Busque por restaurantes da nossa marca!
         </Text>
         <Text style={estilos.descricao}>
           Procure pelo nome, CEP ou perto de você...
         </Text>
       </View>
     );
   }
 }
 
 module.exports = PaginaBusca;

O nosso método render() agora traz uma fiel representação de como deve ser usado o JSX e a estrutura que o mesmo provê. Veja que estamos apenas fazendo uso das mesmas declarações do exemplo AloMundo, porém com um pouco mais de conteúdo representado por meio do componente <Text>. A tag <View>, por sua vez, demarca os limites da visão para a mesma estrutura. Na linha 16 podemos ver a associação com o módulo que o arquivo JS representa por si só, permitindo assim que possamos exportar essa classe para uso em outros arquivos.

Antes de testarmos, ainda precisamos atualizar o routing da aplicação para que possamos direcionar o acesso ao novo arquivo criado. Para isso, abra o arquivo index.ios.js e adicione a seguinte linha de código logo após a cláusula de declaração da função require(), no topo do arquivo:

var PaginaBusca = require(‘,/PaginaBusca’);

Além disso, modifique também a chamada à propriedadecomponent (que aponta para BuscadorRestauranteApp) para apontar para PaginaBusca:

component: PaginaBusca

O código do exemplo AloMundo pode ser removido dos diretórios se desejar. Agora é só salvar todos os arquivos e recarregar a página no emulador e o resultado se parecerá com o da Figura 8.

Tela de Buscas de Restaurantes inicial
Figura 8. Tela de Buscas de Restaurantes inicial.

Até o momento, estamos lidando somente com propriedades CSS simples que configuram margens, paddings, cores, etc. Entretanto, existe uma recente adição à especificação do CSS, chamada Flexbox, que é muito útil para a construção de layouts de aplicações desse tipo.


Minha Primeira Single Page Application com React


O React Native faz uso da biblioteca css-layout, uma implementação JavaScript da Flexbox padrão que foi transcrita para as linguagens C (para uso no iOS) e Java (para Android). O Facebook criou essa separação inicialmente com o objetivo de abraçar as múltiplas linguagens que temos para desenvolver tanto para a web, quanto para o mundo móvel.

No nosso aplicativo, o layout padrão assume as características de colunas, comumente chamado de main axis (eixo principal), onde os elementos são empilhados de forma vertical ou horizontal. A posição vertical de cada elemento é determinada através de uma combinação de suas margens, altura e padding. O container também configura a propriedade alignItems com o valor center que determina a posição dos itens filhos no eixo transversal. Nesse caso, tudo findará em um texto alinhado ao centro.

Vejamos agora o que é preciso para adicionar os campos de texto e o botão de pesquisa. Vá ao arquivo PaginaBusca.js novamente e insira o código contido na Listagem 7 ao mesmo, logo após a segunda tag <Text> do método render().

7. Código que monta a estrutura de campo de texto e botão.


  <View style={estilos.flowRight}>
    <TextInput
      style={estilos.searchInput}
      placeholder="Buscar por nome ou CEP"/>
    <TouchableHighlight style={estilos.button}
        underlayColor="#99d9f4">
      <Text style={estilos.buttonText}>OK</Text>
    </TouchableHighlight>
  </View>
  <TouchableHighlight style={estilos.button}
      underlayColor="#99d9f4">
    <Text style={estilos.buttonText}>Buscar por Local</Text>
  </TouchableHighlight>
  </View>

Esse código será responsável por fornecer a estrutura HTML do campo de texto que receberá o nome do restaurante da pesquisa (linha 2), dos botões de Busca (linha 5) e de Busca Por Local (linha 10). Perceba que estamos incutindo alguns elementos de estilo à página que precisam ainda ser implementados na variável de estilos (Listagem 8).

8. Código de adição do estilo aos campos criados.


 flowRight: {
   flexDirection: "row",
   alignItems: "center",
   alignSelf: "stretch"
 },
 buttonText: {
   fontSize: 18,
   color: "white",
   alignSelf: "center"
 },
 button: {
   height: 36,
   flex: 1,
   flexDirection: "row",
   backgroundColor: "#8cc53e",
   borderColor: "#8cc53e",
   borderWidth: 1,
   borderRadius: 8,
   marginBottom: 10,
   alignSelf: "stretch",
   justifyContent: "center"
 },
 searchInput: {
   height: 36,
   padding: 4,
   marginRight: 5,
   flex: 4,
   fontSize: 18,
   borderWidth: 1,
   borderColor: "#8cc53e",
   borderRadius: 8,
   color: "#8cc53e"
 }

Observe que o uso da vírgula é obrigatório para separar cada uma das classes CSS, diferentemente do que fazemos com arquivos CSS originais. Caso você esqueça, uma mensagem de erro aparecerá na tela mostrando a linha e a causa do mesmo.

Reexecute a aplicação e você verá o resultado exibido pela Figura 9.

Tela de Buscas de Restaurantes com
campos de entrada e estilo aplicado
Figura 9. Tela de Buscas de Restaurantes com campos de entrada e estilo aplicado.

Você deve ter percebido que usamos um atributo CSS um tanto quando diferente nas declarações de estilo, o flex (linhas 13 e 27). Esse atributo serve para configurar dimensões de forma flexível, para que as mesmas se adaptem aos diferentes tamanhos de telas dos dispositivos em que o aplicativo for executado.

Além disso, os botões que criamos não são necessariamente botões, uma vez que não usamos tags específicas para isso. Eles funcionam mais como labels que podem ser clicadas. Isso ocorre em vista da facilidade com que o UIKit disponibiliza os componentes para serem impressos na tela, de forma a otimizar o trabalho principalmente no que se refere à quantidade de elementos e tags diferentes.

Adicionando estado aos componentes

Cada componente do React Native tem seu próprio estado de objeto, que é usado como armazenamento chave-valor. Antes de qualquer componente ser renderizado, ele precisa antes ter seu estado inicial configurado.

A primeira alteração, portanto, consiste em incluir um construtor antes da função render(). Adicione o código da Listagem 9 antes da mesma.

9. Construtor para aplicação de estado aos componentes.

constructor(propriedades) {
    super(propriedades);
    this.state = {
      stringPesquisa: "osasco"
    };
  }
   
  // Alterações no campo de texto
  <TextInput
    style={estilos.searchInput}
    value={this.state.stringPesquisa}
    placeholder="Buscar por nome ou CEP"/>

Agora o nosso componente tem uma variável state com um atributo interno stringPesquisa configurado com o valor inicial de "osasco". Além disso, precisamos referenciar o local de chamada a esse estado, através da propriedade value do campo de texto, assim garantimos que o objeto estará preenchido com o seu respectivo estado quando a tela iniciar, e o método constructor() nos garante isso.

Em seguida, precisamos configurar também a função ouvinte que observará sempre que houver mudanças no texto do mesmo campo. Veja na Listagem 10 o código que precisamos para implementar essa funcionalidade.

10. Código do ouvinte de mudança no texto e alteração no campo de input.

onSearchTextChanged(event) {
    this.setState({ stringPesquisa: event.nativeEvent.text });
  }
   
  <TextInput
    style={estilos.searchInput}
    value={this.state.stringPesquisa}
    onChange={this.onSearchTextChanged.bind(this)}
    placeholder="Buscar por nome ou CEP"/>

Agora, sempre que o conteúdo do campo de texto mudar teremos essa função sendo executada e, portanto, a propriedade state sendo atualizada com o novo valor. Se o leitor desejar, poderá colocar algumas linhas de log para debugar o estado de quando o texto mudar.

Implementação da pesquisa

Agora precisamos manipular de fato o click no botão de “Ok”, criando uma API de requisições que se encaixe e indicando ao usuário que a query está sendo processada. Então, para início de implementação, adicione um novo atributo à propriedade state e em seguida crie um novo componente para se encarregar de exibir a imagem de carregando, conforme demonstrado na Listagem 11.

11. Criando código para lidar com o loading da página.


  this.state = {
    stringPesquisa: "osasco",
    isCarregando: false
  };
   
  // Dentro do método render()
  var carregando = this.state.isCarregando ?
    ( <ActivityIndicatorIOS
        hidden="true"
        size="large"/> ) :
    ( <View/>);

Essa nova propriedade salvará o estado do processamento da página, ou seja, sempre que alguma atividade estiver sendo executada no plano de fundo, esse valor deverá ser setado para true e o componente "carregando" manipulado via código.

Para que ele seja adicionado à página, é preciso que usemos o JSX para definir isso. Para tanto, adicione a seguinte linha de código ao final do último componente TouchableHighlight:

{carregando}

E adicione a seguinte propriedade dentro da tag TouchableHighlight responsável por renderizar o texto “OK”:

onPress={this.onSearchPressed.bind(this)}

Essa propriedade se encarregará de efetuar a associação entre o pressionamento do botão e o método que executará o código em questão. Em seguida, adicione os dois métodos definidos na Listagem 12 ao mesmo arquivo.

12. Métodos para lidar com execução da query.


  _executarQuery(query) {
         this.setState({ isCarregando: true });
  }
   
  onSearchPressed() {
         var query = this.state.stringPesquisa;
         this._executarQuery(query);
  }

O método _executarQuery() será responsável por conter toda a lógica de busca do nosso aplicativo. A partir daqui o leitor decidirá aonde deseja buscar as informações, seja num Web Services disponibilizado na web que será acessado via API HTTP do JavaScript, seja num arquivo de texto, base de dados do dispositivo, etc. O importante é que agora temos toda a estrutura de busca pronta para receber o código que você quiser. Inicialmente, a única responsabilidade que ele tem até agora é exibir o loading de carregando.

O segundo método, por sua vez, está designado apenas para verificar quando o nosso botão é pressionado. Esse tipo de estrutura é muito semelhante à de componentes GUI que temos no Swing do Java ou no C#, por exemplo.

O leitor pode e deve espalhar trechos de console.log pelo código de modo a conseguir enxergar o que está acontecendo no mesmo, debugá-lo. A partir disso, agora é só salvar novamente os arquivos e recarregar o emulador, clicando no campo OK. Você deverá encontrar algo semelhante à Figura 10.

Tela de Buscas de Restaurantes com o
campo de carregando exibido
Figura 10. Tela de Buscas de Restaurantes com o campo de carregando exibido.

Também precisamos configurar as ações que serão tomadas pelo aplicativo quando:

1. Não encontrarmos nenhum resultado na busca, mostrando, assim, uma mensagem de validação (lembrando que essa não é a única mensagem que o app vai manusear, portanto é interessante criar uma estrutura genérica o suficiente para abraçar outras situações).

2. Encontrarmos algum resultado, exibindo-o em uma segunda tela com uma listagem preparada para tal.

O primeiro ponto é mais simples de resolver, basta que criemos um novo atributo dentro da variável state para salvar tais mensagens. Para isso, siga os passos descritos na Listagem 13.

13. Criando estrutura de exibição de mensagens no app.


  this.state = {
    stringPesquisa: "brasil",
    isCarregando: false,
    msg: ""
  };
   
  // Depois adicione o componente de texto para exibir as msgs
  <Text style={estilos.descricao}>{this.state.msg}</Text>

Lembrando que o segundo componente de texto deve ser adicionado após o componente de carregando que implementamos antes.

Esse código representa apenas a estrutura, mas ainda precisamos manipulá-la dinamicamente. Antes disso, vamos implementar agora a página que será responsável por exibir a listagem dos restaurantes. Crie um novo arquivo JavaScript de nome PaginaResultado.js e adicione o conteúdo inicial da Listagem 14 a ele.

Listagem 14. Criando página de listagem dos resultados.


"use strict";
   
  var React = require("react-native");
  var {
    StyleSheet,
    Image, 
    View,
    TouchableHighlight,
    ListView,
    Text,
    Component
  } = React;

Este código inicial apenas mapeia as variáveis que usaremos no restante da listagem, que compreendem desde o estilo para a imagem do restaurante, o componente ListView que se encarregará de efetuar o loop de listagem em si, textos, etc. A mesma estrutura de require() se mantém e deve ser seguida para cada nova página criada.

Agora precisamos criar o componente em si. Para isso adicione ao fim do arquivo o conteúdo da Listagem 15.

15. Componente de exibição dos resultados.


 class PaginaResultado extends Component {
 
  constructor(propriedades) {
    console.log(propriedades.listings)
    super(propriedades);
    var dados = new ListView.DataSource(
      {rowHasChanged: (r1, r2) => r1.guid !== r2.guid});
    this.state = {
      dados: dados.cloneWithRows(propriedades.listings)
    };
  }
 
  renderRow(rowData, sectionID, rowID) {
    
    return (
      <TouchableHighlight 
          underlayColor="#dddddd">
        <View>
          <View style={estilos.rowContainer}>
            <Image style={estilos.thumb} source={{ uri: rowData.img_url }} />
            <View  style={estilos.textContainer}>
              <Text style={estilos.price}>{rowData.nome}</Text>
              <Text style={estilos.title} 
                    numberOfLines={1}>{rowData.endereco}</Text>
            </View>
          </View>
          <View style={estilos.separator}/>
        </View>
      </TouchableHighlight>
    );
  }
 
  render() {
    return (
      <ListView
        dataSource={this.state.dados}
        renderRow={this.renderRow.bind(this)}/>
    );
  }
 
 }

 module.exports = PaginaResultado;

Esse componente traz algumas configurações semelhantes às que fizemos antes, mas outras são novidades, a saber:

  • Na declaração do construtor (linha 3) temos dentro um logging sendo efetuado apenas para conferir o atributo de listagem que receberemos como parâmetro do construtor. Na linha 6 o leitor pode observar a chamada explícita ao construtor da classe DataSource, interna à API do React Native. Através dela, conseguimos criar um objeto inteligível o suficiente para guardar os dados da listagem em forma de linhas de uma tabela de uma coluna só. Na linha 8 estamos salvando o valor na variável state que será usado posteriormente para exibição de cada linha em específico. Veja que na linha 9 chamamos a função cloneWithRows() que se encarregará de converter a nossa lista “listings” (que deve ser um array obrigatoriamente) em um objeto do tipo DataSource.
  • A segunda função, representada na linha 13, se encarrega de retornar as tags decoradas (decoradas, por que usam uma espécie de padrão Decorator para desenhar a resposta final) com os dados iterados da lista convertida. As tags não apresentam novidade, exceto pela tag <Image> exposta na linha 20, que se encarrega de receber a URL da imagem a ser exibida. Perceba que, exceto pelos estilos, todos os componentes têm seus valores extraídos do parâmetro rowData, que representa o objeto atual da iteração.
  • Na linha 33 definimos a função de renderização render(), que simplesmente informa a fonte de dados (dataSource) a ser exibida, bem como a função que se encarregará de imprimir cada uma das linhas, renderRow().
  • Por fim, na linha 43, temos a exportação da página como módulo, conforme fizemos para a página de buscas.

Essa codificação ainda precisa receber a variável de estilos que está usando. Para isso, adicione o conteúdo da Listagem 16 logo após a declaração var, correspondente ao objeto de estilos da página de resultados.

16. Componente de exibição dos resultados.

var estilos = StyleSheet.create({
    thumb: {
      width: 80,
      height: 80,
      marginRight: 10
    },
    textContainer: {
      flex: 1
    },
    separator: {
      height: 1,
      backgroundColor: "#dddddd"
    },
    price: {
      fontSize: 20,
      fontWeight: "bold",
      color: "#8cc53e"
    },
    title: {
      fontSize: 20,
      color: "#656565"
    },
    rowContainer: {
      flexDirection: "row",
      padding: 10
    }
  });

Antes de efetuarmos os testes, ainda precisamos efetuar mais dois passos. O primeiro consiste em adicionar a seguinte linha ao início do arquivo PaginaBusca.js

 var PaginaResultado = require("./PaginaResultado");

Esse código constitui a referência à próxima página, para que assim tenhamos o componente disponível a nível de código na página de buscas e possamos usá-lo para efetuar o controle de navegação.

Já o segundo passo se refere à criação de todo o código de busca, de fato. Transcreva o código presente na Listagem 17 para a sua função _executarQuery() da página PaginaBusca.js.

17. Código de busca dos restaurantes, de fato.


    _executarQuery(query) {
      this.setState({ isCarregando: true });
  
      var rests = new Object();
      rests[0] = {nome: "Restaurante Espaço Brasil", endereco: "Rua Teste 1", 
        img_url: "http://media-cdn.tripadvisor.com/media/photo-s/02/db/51/26
          /espaco-brasil-itacare.jpg"};
      rests[1] = {nome: "Restaurante Sabor do Brasil", endereco: "Rua Teste 1", 
        img_url: "http://www.portaldepaulinia.com.br/images/stories/2012/abril
          /Sabor-du-Brasil.JPG"};
      rests[2] = {nome: "Restaurante Tempero Manero", endereco: "Rua Teste 1", 
        img_url: "http://image.slidesharecdn.com
          /apresentaofranquia-140918120431-phpapp01/95
         /conheca-a-maior-franquia-de-restaurantes-do-brasil-1-638.jpg"};
  
      var achou = false;
      var result = new Array();
      for (var i = 0; i < 3; i++) {
        var item = rests[i];
        if (item.nome.toLowerCase().indexOf(query.toLowerCase()) > -1) {
          achou = true;
          result.push(item);
        }
      }
  
      if (achou) {
        this.setState({ isCarregando: false , msg: "" });
        this.props.navigator.push({
          title: "Resultados",
          component: PaginaResultado,
          passProps: {listings: result}
        });
      } else {
        this.setState({ isCarregando: false , msg: "Nenhum dado encontrado" });
      }
    }

Vejamos alguns detalhes da listagem:

  • Da linha 4 a linha 7 estamos criando uma estrutura em mock para fornecer dados de teste para o nosso aplicativo. Perceba que estamos criando um objeto genérico rests que conterá cada um dos dados de cada restaurante. O preenchimento se dá através da definição de um nome para cada valor dentro do próprio vetor (nome, endereco, etc.).
  • Na linha 11 efetuamos um loop simples sobre todos os elementos verificando se o texto recebido como query por parâmetro está contido dentro de algum dos nomes dos restaurantes de exemplo. A função toLowerCase() (linha 13) se faz necessária para que não tenhamos de validar se o usuário digitou algo em maiúsculo ou minúsculo. Se encontrarmos alguma coisa, configuramos a variável achou com o valor true e adicionamos cada um dos itens ao vetor result, caso contrário, nada acontece.
  • Na linha 19 é onde verificamos se o teste passou e, caso positivo, configuramos o state para parar o loading, assim como setamos a propriedade props com os dados necessários para forçar a navegação, passando como parâmetro o array de elementos encontrados (linha 24). Caso negativo, avisamos ao usuário que nenhum dado foi encontrado para o valor informado.

Pronto, agora é só salvar os arquivos e reexecutar a aplicação. Você pode acompanhar os fluxos de quando nenhum dado é encontrado e de quando os dados são exibidos com sucesso nas Figuras 11 e 12, respectivamente.

Resultado de quando nenhum dado é
encontrado
Figura 11. Resultado de quando nenhum dado é encontrado.
Resultado dos dados encontrados com
sucesso
Figura 12. Resultado dos dados encontrados com sucesso.

Dados via Web Service

Até o momento os dados do nosso aplicativo estão fixos no código, em forma de mock. Para simular uma situação real, em vez de criarmos todo o aparato server side para lidar com um Web Service de verdade, podemos usar o modo raw do Github, isto é, criamos um arquivo com o conteúdo JSON que simularia uma resposta a um serviço e o consumimos no nosso aplicativo.

Para fazer isso, você precisará ter uma conta no Github. Crie um novo repositório e em seguida vá até a opção “Create a new file here”. Dê um novo ao seu arquivo e adicione o conteúdo da Listagem 18 ao mesmo.

18. Código JSON para simular requisição de Web Service.


    {
  rests: [
         {nome: "Restaurante Espaço Brasil", endereco: "Rua Teste 1", 
            img_url: "http://media-cdn.tripadvisor.com/media/photo-s/02/db/51/26
             /espaco-brasil-itacare.jpg"}, 
         {nome: "Restaurante Sabor do Brasil", endereco: "Rua Teste 1",
           img_url: "http://www.portaldepaulinia.com.br/images/stories/2012/abril
            /Sabor-du-Brasil.JPG"}, 
         {nome: "Restaurante Tempero Manero", endereco: "Rua Teste 1", 
          img_url: "http://image.slidesharecdn.com
            /apresentaofranquia-140918120431-phpapp01/95
           /conheca-a-maior-franquia-de-restaurantes-do-brasil-1-638.jpg"}]
  }

Ele representa apenas o código JSON de retorno com os mesmos dados que tínhamos no mock. Após isso, o arquivo será criado e as opções apresentadas na Figura 13 aparecerão. Clique na opção “Raw” e copie a URL para a qual você será redirecionado. Agora precisamos alterar o arquivo do index.ios.js para se adequar às mudanças (Listagem 19).

 Opções disponibilizados para arquivo
pelo Github
Figura 13. Opções disponibilizados para arquivo pelo Github.

19. Código de alteração do arquivo index.ios.js para receber JSON.


 var REQUEST_URL =
   "https://raw.githubusercontent.com/JulioSampaio/devmedia/master/restaurantes.json";  

  getInitialState: function() {
    return {
      rests: null,
    };
  },
  
  fetchData: function() {
    fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          rests: responseData.rests,
        });
      })
      .done();
  },

  _executarQuery(query) {
    this.setState({ isCarregando: true });

    var achou = false;
    var result = new Array();
    for (var i = 0; i < 3; i++) {
      var item = this.state.rests[i];
      if (item.nome.toLowerCase().indexOf(query.toLowerCase()) > -1) {
        achou = true;
        result.push(item);
      }
    }

    if (achou) {
      this.setState({ isCarregando: false , msg: "" });
      this.props.navigator.push({
        title: "Resultados",
        component: PaginaResultado,
        passProps: {listings: result}
      });
    } else {
      this.setState({ isCarregando: false , msg: "Nenhum dado encontrado" });
    }
  }

Vejamos alguns detalhes sobre a implementação:

  • Na linha 1 adicionamos uma variável global que recebe o valor da URL raw do Github. Cole aqui a URL que você copiou anteriormente.
  • Na linha 3 instanciamos a função getInitialState() que se encarregará de resetar a variável rests.
  • A função fetchData() da linha 9 se encarregará de efetuar a chamada HTTP à URL em questão e retornar os dados em formato JSON. No fim, ela associa o vetor de restaurantes à variável global rests.
  • Na função _executarQuery (linha 20) a única mudança que precisamos fazer é referenciar a variável global rests na linha 26.

Após isso, basta salvar os arquivos e reexecutar a aplicação. O resultado das telas permanecerá igual com exceção do tempo de carregamento, que será maior em vista do acesso a um recurso na web.

O leitor pode ficar à vontade para inserir quaisquer tipos de dados que deseje e deixar o estilo da sua listagem mais elegante e completo. Da mesma forma, a quantidade e qualidade dos dados podem ser mudadas a bel prazer, uma vez que estamos lidando com elas de forma “mockada”.

Em uma situação de mundo real, tais dados deveriam vir de algum lugar mais confiável, como um Web Service. Se for esse o caso, você pode construir o seu Web Service (que aqui não será mostrado para não fugir ao escopo do artigo) na tecnologia que desejar e o referenciar pela aplicação. É possível até disponibilizá-lo em nível localhost, usando Java, PHP ou Python, por exemplo, para que você possa efetuar todos os testes sem ter de publicar o serviço em uma hospedagem oficial.

Uma vez com tudo configurado, você poderá implementar requisições HTTP ao seu serviço e consumir a resposta dos dados que terão praticamente o mesmo modelo JavaScript/JSON que trabalhamos aqui no artigo.

Ademais, o leitor também pode implementar mais casos de uso que envolvam navegação entre telas, como a que fizemos aqui, como uma tela de detalhamento de cada restaurante, por exemplo. Ao clicar em um dos itens listados, você faria uma consulta ao mesmo serviço e exibiria os detalhes daquele restaurante, como telefone, um mapa Google com as coordenadas, slider de fotos, vídeo, etc. Você também pode configurar a busca por CEP usando os mesmos serviços de mapa que o Google, Bing, etc. disponibilizam.

Enfim, a criatividade é a chave a partir de agora. O mais importante é saber que com a junção entre o React Native e o JavaScript/CSS/afins você terá recursos muito poderosos em mãos que o fará criar aplicativos cada vez melhores. Bons estudos!

Referências:

Site oficial da biblioteca React JS.
http://facebook.github.io/react/

Site oficial da biblioteca React Native.
https://facebook.github.io/react-native/

Site da Conferência React, ocorrida no Facebook, CA.
http://conf.reactjs.com/

Site de download do Node.js.
https://nodejs.org/download/

Site da Conferência React, ocorrida no Facebook, CA.
http://conf.reactjs.com/

Links Úteis
Colabore com o nosso Fórum