CineFortaleza 33 e 1/3: Como foi desenvolvido o novo aplicativo com React Native

O IMDb foi o primeiro site que visitei na Internet. Assisti Jurassic Park no cinema em uma idade muito vulnerável — não tive outra opção se não me tornar um ávido frequentador dos cinemas locais. Minha admiração pelo IMDb e por cinema se manteve, e em janeiro de 2014 publiquei um pequeno serviço web chamado CineFortaleza, com a proposta de trazer os filmes em cartaz em Fortaleza-CE, só que com alguns pequenos shenanigans: os filmes eram atualizados automaticamente todos os dias buscando nos sites de cada cinema, e ordenados pela nota no IMDb. Assim os mais bem votados pela maior comunidade cinéfila do planeta estariam em cima da lista.

Para a nova versão, queria algo mais fluido e resolvi experimentar o React Native. Como qualquer desenvolvedor que já publicou seu código em produção bem sabe, muitas vezes os detalhes mais complicados de novas tecnologias não vão aparecer nos famosos hello, world! e exemplos de to-do lists que estão em tantos lugares. Eles vão aparecer quando você usar aquela tecnologia no seu projeto. Por isso, acredito que compartilhar a minha experiência de ~vida real~ pode ser de algum proveito caso você se interesse por algumas das tecnologias utilizadas. Afinal de contas, o diabo está nos detalhes.

Importante também destacar que a versão do app com React Native só está disponível para iOS, no momento. Sem mais delongas, vamos ao que interessa:

React Native + CineFortaleza

Primeiro de tudo, posso adiantar que a documentação do React Native (que chamarei de RN em futuras referências neste texto) é muito boa. Ela é recheada de exemplos, e não é difícil encontrar repositórios públicos para estudar diversas implementações e suas nuances. Sabendo disto, não vou tomar o seu tempo explicando como usar o RN — você encontra isso fácil na internê. Vou focar este texto na aplicação dele, as dificuldades e as soluções.

Para fins de referência, utilizei a versão 0.42.0 do React Native.
Essas são as 3 principais telas do aplicativo: uma listagem de filmes, as sessões de cada filme e um filtro de cinemas.

Ambiente

Além do trivial, tive que fazer uma pequena configuração para que o meu Sublime exibisse de uma forma agradável os arquivos JSX do React. Usei um pacote do sublime chamado babel-sublime e precisei editar as preferências do projeto para associar os arquivos .js com a syntax adequada:

{
"folders": [
{
"path": "/Users/brunocavalcante/workspace/js/CineFortaleza",
"folder_exclude_patterns": [...],
},
],
"syntax_override": {
"\\.js$": ["Babel", "JavaScript (Babel)"]
}
}
Antes e o depois da syntax coloring correta, com um código real do aplicativo de bônus

Ambiente pronto, então vamos às telas:

Filmes em cartaz

Naturalmente, essa foi a primeira funcionalidade a ser implementada. Ela parece super simples — mas é cheia de pequenos detalhes:

  • A lista de filmes e sessões deve ser gravada localmente sempre que for buscada da API do CineFortaleza.
  • O app só deve buscar a lista da API quando necessário. Nos outros casos (incluindo acessar sem internet), ele deve usar os dados salvos localmente, se não fizer muito tempo desde que a programação foi salva.
  • O usuário deve poder forçar a atualização da lista via pull-to-refresh. Ao buscar os filmes, é absolutamente crítico que seja exibido a marca do CineFortaleza (vulgarmente conhecida como pipoquinha). E ela precisa se mexer. De vez em quando.
Coloquei um sleep para que a pipoquinha aparecesse mais tempo. :)

Para a listagem dos filmes, usei o componente ListView. É um caso bem tradicional de uso dele, então a implementação disto foi bem suave. Para salvar os dados localmente, fiz uso do AsyncStorage. Essa parte de puxar os filmes da API, renderizar em um ListView e salvar eles localmente foi sem dúvidas a funcionalidade mais simples do projeto. O ListView já suporta pull-to-refresh, o que é ótimo. A lógica da pipoquinha ficou com o componente NoMovies, já ilustrado acima. Enquanto o DataSource da ListView não existe, quem assume a frente dessa tela é o NoMovies.

Sessões do Filme

Ao escolher um filme, o app deve navegar para a próxima tela e exibir as suas sessões. A navegação foi um tópico interessante — comecei usando o componente Navigator, mas acabei optando pelo NavigatorIOS (visto que o app está sendo feito primeiro para iOS). Abaixo, o código que dispara a navegação da página de filmes para as sessões:

renderRow(movie: Object, sectionID, rowID, highlightRow) {
return (
<MovieCell key={movie.id} movie={movie} onSelect={() => this.selectMovie(movie)} />
)
}
...
selectMovie(movie: Object) {
this.props.navigator.push({
title: "Sessões",
component: MovieScreen,
passProps: {
movie: movie,
cinemaId: this.state.cinemaId
}
})
}

E o resultado:

Para a renderização das sessões, comecei com uma outra ListView, porém esbarrei em um problema: a ListView incluía os cinemas e as sessões, e os detalhes do filme (que ficam logo acima das sessões) estava fora da ListView, e portanto fora do scroll da ListView. Acabei optando por renderizar as sessões somente com componentes View, o que acabou sendo mais simples e resolveu o problema de eles estarem em áreas de rolagem diferente.

Filtragem por cinema

Se a listagem de filmes foi a parte mais fácil, essa foi a mais difícil do projeto em React Native. Para o menu Drawer, usei o componente react-native-drawer. A parte complicada foi fazer a escolha de cinema afetar o componente HomeScreen, que possui o ListView com os filmes em cartaz. A solução que encontrei para isso foi utilizando EventEmitter.

Basicamente, a aplicação instancia um EventEmitter, e passa ele para o HomeScreen, que registra um listener para um evento que eu chamei de changeCinema. Esse listener recebe o cinema escolhido e filtra o DataSource da lista.

O menu de cinemas também recebe a instância do emitter, e emite um changeCinema quando o usuário escolhe o cinema. Mas, vamos ao código:

HomeScreen registrando o listener:

class HomeScreen extends Component {
constructor(props) {
...
this.props.emitter.addListener('changeCinema', this.applyFilter, this)
}
...
}
Ps.: this.props.emitter é uma instância de EventEmitter.

Agora para a emissão do evento:

class Menu extends Component {
selectCinema(cinemaId = null) {
...
this.props.emitter.emit('changeCinema', { cinemaId: cinemaId })
...
}
...
}

E voilá:

Outra coisa interessante é que a tela de Sessões lista primeiro o cinema escolhido, e os outros cinemas ficam abaixo – isso caso exista um cinema escolhido.

Outros Componentes

Além dos componentes citados, aqui uma lista da turma do arrepio de componentes que me foram utilizados:


A comunidade e o framework do React Native estão, na falta de uma palavra melhor, em polvorosa e novos componentes chegam toda semana. Creio que em menos de 6 meses muitas das soluções que usei nesta fase do projeto já estarão obsoletas – e isso não tem sido incomum no ciclo de desenvolvimento do React Native e não tem nada de errado com isso, imo. O framework ainda está em beta e é mais do que necessário experimentar para chegar às soluções mais adequadas. Com essa mentalidade, o futuro do React Native parece definitivamente promissor.

Ah, você encontra o link para baixar o aplicativo aqui.