Criando App para Smart TVs

Vamos mostrar o desen­volvi­men­to de uma app sim­ples para Smart Tv.

Ten­do a ideia de ten­tar pas­sar por boa partes dos prob­le­mas que temos quan­do desen­volve­mos app para TVs, pen­sei em vários tipos difer­entes de exem­p­lo de apps: gale­ria de fotos, app de stream­ing de músi­ca, app de stream­ing de vídeos…

O Projeto:

Com isso em mente, a escol­ha que mais se encaixou ness­es req­ui­s­tos foi: app que sim­u­la um catál­o­go de animes/mangá.
Após essa série de posts, se vocês quis­erem, eu faço um mais “real”, com request a várias APIs, play­er para tocar algo, entre out­ras coisas (Não se esqueça de comen­tar!!). Será um app rel­a­ti­va­mente sim­ples, porém como o post ficará muito logo eu irei cri­ar uma série de posts. E lem­bran­do: esse será um pro­je­to web host­ed foca­do para as Smart Tvs da Samung (Tizen) e LG (WebOs).

Nesse pro­je­to nós ire­mos des­de a cri­ação de uma app na sua máquina até você ver ela fun­cio­nan­do na sua TV 😆. Com isso ensinarei como faz­er uma app e quais seri­am os pas­sos para pub­licar a sua app.

Ferramentas:

Basi­ca­mente nós ire­mos uti­lizar essa stack para desen­volver:

  • React
  • Web­pack
  • Jest
  • HTML/CSS
  • Stack­over­flow

Porém, você é livre para escol­her a sua, poden­do uti­lizar qual­quer frame­work para desen­volver. Vue.js, React, Flut­ter, JS Vanil­la, são alguns exem­p­los. Você só não pode esquercer de duas pre­mis­sas: com­pat­i­bil­i­dade e per­for­mance! Então vamos faz­er essa bagaça fun­cionar!!!

Configurando o projeto:

Aqui é aque­la con­fig­u­ração padrao: aque­le nos­so create-react-app bási­co (se você não tem o cre­ate-react-app é só usar o npm install -g create-react-app). Com isso, já começamos a ter a nos­sa estru­tu­ra ini­cial.

1
$ create-react-app my-first-smart-tv-app

Nota: Para fins práti­cos e edu­ca­cionais, eu não irei otimizar a nos­sa app ao máx­i­mo, me atentarei para o fun­ciona­men­to bási­co de uma app.

Con­tin­uan­do, ago­ra está na hora de adi­cionar o babel e o web­pack, segue os coman­dos:

1
2
3
4
5
6
# Webpack
$ yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin html-loadert
# babel
$ yarn add -D @babel/core babel-loader @babel/preset-env @babel/preset-react babel-preset-es2015
# Sass/css
$ yarn add -D sass-loader node-sass css-loader mini-css-extract-plugin

É necessário faz­er algu­mas con­fig­u­rações para o fun­ciona­men­to do webpack/babel após a insta­lação, como eu mexi em vários arquiv­os, aqui está o com­mit com essas mod­i­fi­cações.

Com isso nós já temos a estru­tu­ra da nos­sa APP.

Começando o desenvolvimento:

Antes que eu me esqueça, estarei postan­do essa app no github, com isso se você quis­er ape­nas o resul­ta­do final, estará tudo doc­u­men­ta­do lá.

Ago­ra vamos começar a codar de ver­dade, para isso vamos faz­er um request para a API que vai nos entre­gar os dados que a gente pre­cisa, para isso vamos uti­lizar o Jikan. O Jikan é uma API não ofi­cial do My Ani­me List, ela é bem com­plet­inha para o que ire­mos faz­er 😆. E para ele existe um wrap­per em js (jikan­js) para facil­i­tar ain­da mais a nos­sa vida, para insta­lar bas­ta rodar.

1
$ yarn add jikanjs

Com isso já temos aces­so a api, você pode faz­er um teste colo­can­do no App.js o seguinte tre­cho de códi­go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
render() {
  jikanjs
  .loadSeasonLater()
  .then((response) => {
      response.anime.forEach(element => {
          console.log(`${element.mal_id}: ${element.title} - ${element.image_url} - ${element.type}`);
      })
  }).catch((err) => {
      console.error(err); // in case a error happens
  });

  return <h1> Hello World :) </h1>;
}

Abra o seu con­sole e você verá a respos­ta do servi­dor.

Vamos colo­car ess­es dados na tela, e para isso vamos cri­ar uma estru­tu­ra HTML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
tmpl(anime){
  const {image_url, title, mal_id, type} = anime

  return (
    <div className="poster-wrapper" id={mal_id} key={mal_id}>
      <figure>
        <span>{type}</span>
        <img src={image_url} alt={title} />
        <p>{title}</p>
      </figure>
    </div>
  )

}

e depois vamos faz­er o request, para isso vamos adi­cionar o state e o componentDidMount:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
constructor(props) {
  super(props)
  this.state = {
      animes: []
  }
}

componentDidMount() {
  let animes = []
  jikanjs
  .loadSeasonLater()
  .then((response) => {
      response.anime.forEach(element => {
          animes.push(element);
      })
      this.setState({ animes })
  }).catch((err) => {
      console.error(err); // in case a error happens
  });
}

E ago­ra vamos adi­cionar no render o con­teú­do.

1
2
3
4
render() {
    let animes = this.state.animes;
    return animes.map((anime) => (anime.type === 'TV' ? this.tmpl(anime) : null))
}

Pron­to, com isso já temos todos os itens sendo exibidos na pági­na!

Vamos passar a primeira maquiagem:

A ideia aqui é só tornar a nos­sa visu­al­iza­ção mel­hor, não tem mui­ta regra e ain­da nem adi­cionei um pré-proces­sador.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
body {
  background-color: #0f0f0f;
  color: #f1f1f1;
}

.poster-wrapper {
  display: inline-block;
  text-align: center;
  width: 310px;
}

.poster-wrapper--active {
  outline: 1px solid #f1f1f1;
}

Test, Jest everywhere…

Nós não podemos esque­cer de faz­er os testes da nos­sa app (ou de qual­quer códi­go que façamos), para isso vamos insta­lar o jest:

1
2
$ yarn add -D jest babel-jest enzyme jest-environment-enzyme jest-enzyme enzyme-adapter-react-16 identity-obj-proxy
$ yarn jest --init

na lin­ha 2 a gente cria a con­fig­u­ração ini­cial do jest auto­mati­ca­mente, é só seguir a sequên­cia jsdom, y, y, com isso ele cria um arqui­vo chama­do jest.config.js. Nele faça a mod­i­fi­cação nas lin­has 82, 129, 135 e 144:

1
2
3
4
5
6
7
8
moduleNameMapper: { // 82
  "\\.(css|sass)$": "identity-obj-proxy",
},
setupFilesAfterEnv: ["<rootDir>/src/setupTests.js"], // 129
testEnvironment: "enzyme", // 135
testMatch: [ // 144
  "**/*.test.js",
],

Tam­bém pre­cisamos cri­ar o arqui­vo src/setupTest.js e den­tro dele colo­car:

1
2
3
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });

Com isso só fal­ta alter­ar o nos­so package.json para rodar os testes e cov­er­age:

1
2
3
4
5
6
7
8
{
  ...
  scripts: {
    ...
    "test": "jest",
    "test:coverage": "jest --coverage"
  }
}

Tam­bém adi­cionei o ESLint, para ver como ficou, olhe esse com­mit.

Navegação

Para faz­er a nave­g­ação, eu vou adi­cionar uma classe para indicar a seleção:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
tmpl(anime, i) {
    ...
        <div className={`poster-wrapper ${this.state.activeItem === i ? 'poster-wrapper--active': null}`} id={mal_id} key={mal_id}>
    ...
}

render() {
    ...
    return animes.map((anime, i) => (anime.type === 'TV' ? this.tmpl(anime, i) : null))
    ...
}

e ago­ra a gente ouve os even­tos do tecla­do:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
onKeyDown(e) {
    const { activeItem, animes } = this.state
    let newActiveItem = activeItem
    switch (e.keyCode) {
      case 37: // left
        if (activeItem !== 0) {
          newActiveItem -= 1
        }
        break

      case 39: // right
        if (activeItem < animes.length) {
          newActiveItem += 1
        }
        break

      case 40: // down
        if (activeItem < animes.length) {
          if (activeItem + 4 > animes.length) {
            newActiveItem = animes.length
          } else {
            newActiveItem += 4
          }
        }
        break
      case 38: // up
        if (activeItem < animes.length) {
          if (activeItem - 4 < 0) {
            newActiveItem = 0
          } else {
            newActiveItem -= 4
          }
        }
        break
      default:
        break
    }
    this.setState({
      activeItem: newActiveItem
    })
  }

para essa função fun­cionar a gente ain­da pre­cisa adi­cionar as seguintes lin­has:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
constructor(props) {
  ...
  this.onKeyDown = this.onKeyDown.bind(this)
}
componentDidMount() {
  ...
    this.enableKeyEvent()
}

componentWillUnmount() {
    this.disableKeyEvent()
}

enableKeyEvent() {
    document.addEventListener('keydown', this.onKeyDown, true)
}

disableKeyEvent() {
    document.removeEventListener('keydown', this.onKeyDown, true)
}

That‘s it

É isso, para a nos­sa primeira parte isso já é o sufi­ciente para começar a brin­car ( e porque se eu con­tin­uar o post vai demor­ar 3 horas para ser lido 🤪)
Para ver todos os com­mits desse post, acesse essa tag no github

No próx­i­mo post, nós ter­e­mos a con­tin­u­ação dos testes, fix de alguns bugs e cri­ação de algum even­to para quan­do sele­cionar-mos um item.

Qual­quer dúvi­da, crit­i­ca ou sug­estão uti­lizem os comen­tários e até a próx­i­ma!!

Este post é a parte 1 da serie “Crian­do sua primeira APP para Smart TVs

Posts Similares