Dicas de Prototipação: como dar vida a uma ideia?

Tecnologia

Acredito que já tenha ouvido o termo protótipo ou prototipação em algum momento, certo?

Hoje, este será nosso tema. Minha intenção neste artigo é mostrar algumas técnicas que utilizo para prototipar ou para tirar aquela ideia da cabeça. Com certeza já existiram momentos onde você teve uma grande ideia, mas não sabia se conseguiria concluir, ou ver de forma funcional aquela ideia, muitas vezes isso é o que nós leva a desistir de uma ideia que realmente poderia ser sensacional e causar grande impacto no mercado.

Confesso que eu mesmo já tive grandes ideias que, devido ao tempo curto, me levava a nem ao menos tentar fazer um protótipo, pensar em qual tecnologia usar, qual back-end aderir, qual regra de negócio é o certo, já geravam problemas com ansiedade e acabavam me desanimando de tentar desenvolver aquele produto. 

Para evitar isso e tornar qualquer ideia simples de ver em funcionamento, pesquisei sobre metodologias para ver minha ideia funcionando de forma que não precisasse pensar tanto de começo e já conseguir  ter algo visual. Pensando nisso, resolvi escrever esse artigo para ajudar a transformar as ideias de vocês em algo palpável e simples de ter uma visualização da ideia. 

Requisitos mínimos

Para prosseguirmos será necessário conhecer ou ter algumas ferramentas para acompanhar o artigo:

  • NodeJS instalado;
  • Expo Cli instalado;

Ngrok;

UI: o que é ?

UI significa ‘user interface’ (interface de usuário). Isso é a primeira visão da nossa ideia, é nela que vamos decidir onde ficará cada botão,  como deve ser cada tela ou quais informações devem existir. A UI é algo muito importante para aplicação parecer moderna e chamativa. Isso faz com que o usuário se sinta à vontade com nosso projeto, porém, devemos tomar cuidado pois não adianta a ferramenta ser maravilhosa e não funcionar corretamente. Então, temos sempre que estar alinhados tanto com a interface quando a funcionalidade. Outra coisa que devemos saber é que dentro da prototipagem a UI nem sempre significa um layout bonito, já que muitas das vezes ela será apenas um esboço, uma ‘wireframe’. Isso é apenas para tirarmos nossa ideia da cabeça e passarmos para o papel, dessa forma temos algo visual sobre o que desejamos realizar. Te darei um exemplo:

Agora que já vimos o conceito de prototipação e o UI, existe mais um conceito que não utilizaremos para esse protótipo, porém, vamos tocar um pouco no assunto. Além da UI precisamos conhecer o UX (User Experience = Experiência do usuário). Ao contrário de interface, a UX se responsabiliza por dar a melhor experiência para o usuário, aonde cada botão ou cada conceito deve ser aplicado, entretanto, neste momento não falaremos sobre isso.

DICA: Para criar suas interfaces existem ótimas ferramentas. As que mais utilizo são: Figma (Neste é possível até montar um link dos botões para que cada botão direciona para uma tela e assim ter uma espécie de slide com o protótipo do produto) e o draw.io (Ótimo para desenhar esboços). Outro tipo de ferramenta interessante são as ferramentas de mind map para construir um mapa mental de todas características do produto para desenha.

Como simular uma API?

Para testarmos uma ideia, precisamos ter uma API para consumir no nosso protótipo e ter uma noção de como cada peça do nosso protótipo deve ser encaixado. Devido ao fato de criar uma API consumir um tempo maior, eu costumo utilizar de alguns macetes para testar minhas ideias sem ter que me esforçar com lógica, apenas para  “mockar” os dados e ver como ficaria no meu produto, com isto comecei a utilizar uma maravilhosa ferramenta chamada json-server. Essa módulo node é capaz de converter um simples arquivo json em uma api que segue os padrões de uma api REST, utilizando POST, PUT, DELETE, GET e até mesmo utilizar filtros sem colocar um único dedo no back-end.

Vamos ver como criar um “back-end” para testarmos nossa ideia:

O primeiro passo é criar uma pasta para nosso back-end e executar o comando para iniciar um diretório do node:

Assim que o processo for concluído iremos fazer algumas mudanças no package.json:

{
  "name": "fake-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "npx json-server --watch db.json -p 3333"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Após instalado criaremos um arquivo “db.json”.

Neste arquivo deve ter um json com a estrutura que queremos nossa api, para cada índice-pai é uma rota.

PS: Utilize os dados abaixo para o teste

{
  "accountsPayable": [
    {
      "id":1,
      "name": "ENERGIA ELETRICA",
      "cost": 200.00,
      "due_date": "2020-08-20"
    }
  ],
  "revenue": [
    {
      "id":1,
      "name": "TED - Prestação",
      "cost": 2389.55,
      "receive_date": "2020-08-05"
    },
    {
      "id":2,
    "name": "TED - Empresa",
      "cost": 1258.22,
      "receive_date": "2020-08-20"
    },
    {
      "id": 3,
      "name": "Vendas",
      "cost": 225.99,
      "receive_date": "2020-08-05"
    }
  ],
  "users": [
    {
      "id": 1,
      "name": "Aylon Muramatsu"
    }
  ],
  "wallets": [
    {
      "id": 1,
      "name": "Banco do Brasil",
      "agency": "2511",
      "account": "245156",
      "digit": "2",
      "balance": 1599.22
    },
    {
      "id": 2,
      "name": "Itaú",
      "agency": "2511",
      "account": "245156",
      "digit": "2",
      "balance": 3259.22
    },
    {
      "id": 3,
      "name": "Nubank",
      "agency": "2511",
      "account": "245156",
      "digit": "2",
      "balance": 5789.15
    },
    {
      "id": 4,
      "name": "Neon",
      "agency": "2511",
      "account": "245156",
      "digit": "2",
      "balance": 1289.15
    },
    {
      "id": 5,
      "name": "Inter",
      "agency": "2511",
      "account": "245156",
      "digit": "2",
      "balance": 2789.15
    },
    {
      "id": 6,
      "name": "ModalMais",
      "agency": "2511",
      "account": "245156",
      "digit": "2",
      "balance": 4289.15
    },
    {
      "id": 7,
      "name": "Bradesco",
      "agency": "2511",
      "account": "245156",
      "digit": "2",
      "balance": 2689.15
    }
 
  ],
  "transactions": [
    {
      "id": "1",
      "name": "COMPRA 1",
      "cost": 234.99,
      "created_at": "2020-02-01 10:00:00"
    },
    {
      "id": "2",
      "name": "COMPRA CARTAO IFOOD",
      "cost": 60.90,
      "created_at": "2020-02-01 10:00:00"
    },
    {
      "id": "3",
      "name": "COMPRA CARTAO - COMPRA NO ESTABELECIMENTO",
      "cost": 252.30,
      "created_at": "2020-02-01 10:00:00"
    },
    {
      "id": "4",
      "name": "COMPRA CARTAO - COMPRA EM SUPERMERCADO",
      "cost": 135.90,
      "created_at": "2020-02-01 10:00:00"
    },
    {
      "id": "5",
      "name": "COMPRA CARTAO IFOOD",
      "cost": 60.90,
      "created_at": "2020-02-01 10:00:00"
    },
    {
      "id": "6",
      "name": "COMPRA CARTAO - POSTO DE GASOLINA",
      "cost": 50,
      "created_at": "2020-02-01 10:00:00"
    }
  ]
}

Agora que já configuramos o package.json e criamos o arquivo db.json podemos executar o comando npm start:

A Partir deste momento foi criado rotas baseados no json, sendo capaz de excluir, consultar, listar, atualizar ou criar registros através de requisições.

OBS: Nesse passo teremos que usar o ngrok, pois como o react-native exige uma requisição segura, utilizaremos o ngrok para interceptar a api e transforma-la em https, é bem simples de usar, basta instalar o ngrok e utilizar o comando ‘ngrok http porta-da-api’. 

Exemplo: 

ngrok http 3333

Ele te devolverá um link https para sua api.

Agora podemos partir para o aplicativo!

Construindo o Aplicativo

Utilizaremos o expo com bare-overflow para construir o aplicativo, se quiser pode usar o managed sem problema, pode ocorrer apenas algumas diferença ao usar alguns módulos, por isso recomendo seguir este tutorial com bare-overflow

Caso não tenha o expo-cli basta executar o seguinte comando:

npm install expo-cli --global

Para criar o app precisamos gerar um comando com expo:

Após esse comando teremos o projeto criado pronto para desenvolver!

A estrutura que utilizaremos nesse projeto é a seguinte:

  • assets: Utilizado para guardar arquivos estáticos, como imagens
    • imgs: Onde guardaremos as imagens utilizadas no app
  • components: Onde armazenaremos todos nossos componentes
  • hooks: hooks personalizados que podemos vir a criar
  • pages: armazenamento das telas do app
  • service: guardaremos nossas configurações de axios para cada microservice que utilizarmos
  • system: arquivos genéricos para o sistema, como enumeradores, helpers
  • ui:  onde vamos armazenar todo styled dos nossos componentes

Agora que já entendemos como vai funcionar nossas pastas e a arquitetura geral, vamos para nossos primeiros códigos!

OBS: Adicione para a pasta src/assets/imgs uma imagem .png

Vamos começar criando um helper que nos ajudará a mascarar o valor numérico para monetário:

src/system/helper.js

Number.prototype.money = function(properties = {}){
  const {
    precision = 2,
    thousandSeparator = '.',
    decimalSeparator = ',',
    prefix = 'R$ '
  } = properties;
  let re = `\d(?=(\d{3})+${precision > 0 ? '\D' : '$'})`,
    num = this.toFixed(Math.max(0, ~~precision));
  return `${prefix}${num.replace('.', decimalSeparator).replace(new RegExp(re, 'g'), '$&' + (thousandSeparator))}`;
}

Com isso, toda variável do tipo number poderá ser convertida em uma string monetária no formato que decidir, conforme os parâmetros.

Powered by Rock Convert

Com esse primeiro helper feito, podemos adicionar no nosso arquivo index.js

index.js:

import { registerRootComponent } from 'expo';
import './src/system/helper';
import App from './App';
 
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in the Expo client or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

Agora vamos criar um arquivo para armazenar nossa paleta de cores! 

src/system/colors.js

export default {
  GREY: '#D7DDEB',
  WHITE: '#F1F4F6',
  BLUE: '#2224AB',
  ORANGE: '#EE764C',
  CLOUDS: '#ecf0f1',
  MIDNIGHT_BLUE: '#2c3e50'
}

src/service/api.js

import axios from 'axios'
 
export default axios.create({
  baseURL: 'https://f8a4e7237215.ngrok.io',
  headers: {},
})

Criaremos um custom hooks para manipular a requisição, para ficar simples quando for usar

src/hooks/useFetch.js

import api from "../service/api";
 
export function useFetch(url, method = 'get', params = {}, headers = {}){
  return api[method](url, params, headers);
}

Vamos começar a desenvolver nossos componentes e logo em seguida iremos importá-los

src/components/Header.js

import React, { useEffect, useState } from 'react'
import * as UI from '../ui/Header.ui'
import AvatarImage from '../assets/imgs/avatar.png'
import { useFetch } from '../hooks/useFetch'
export default function Header(){
  const [user, setUser] = useState(null);
  const [wallets, setWallets] = useState([]);
 
  async function fetchUser(){
    const {data} = await useFetch('/users/1')
    setUser(data);
  }
 
  async function getWallets(){
    const { data } = await useFetch('/wallets');
    setWallets(data);
  }
  useEffect(() => {
    Promise.all([
      fetchUser(),
      getWallets()
    ])
 
  }, [])
  return (
    <UI.Header>
      <UI.Avatar source={AvatarImage}/>
      <UI.UserInfo>
        <UI.Welcome>Seja Bem-vindo,</UI.Welcome>
        <UI.Username>{user?.name}</UI.Username>
      </UI.UserInfo>
      <UI.BalanceContainer>
        <UI.BalanceTitle>Saldo</UI.BalanceTitle>
        <UI.Balance>{wallets.map(({balance}) => balance).reduce((prev, actually) => (prev + actually), 0).money()}</UI.Balance>
      </UI.BalanceContainer>
    </UI.Header>
  )
}

src/ui/Header.ui.js

import styled from 'styled-components/native/';
import colors from '../system/colors';
import { Dimensions } from 'react-native';
 
const height = Dimensions.get('window').height;
 
export const Header = styled.View`
  background-color: ${colors.BLUE};
  width:100%;
  height: ${height/3}px;
  padding-top:30px;
  padding-left:15px;
  padding-right:15px;
  flex-direction: row;
  top: 0;
  position:absolute;
  z-index:10;
`;
 
 
 
export const Avatar = styled.Image`
  width:60px;
  height:60px;
  border-radius:10px;
  border-color:${colors.WHITE};
  border-width: 2px ;
 
`
 
export const Username = styled.Text`
  color: ${colors.WHITE};
  font-size:18px;
`
 
export const Welcome = styled.Text`
  font-size: 14px;
  color: ${colors.WHITE}
`;
export const UserInfo = styled.View`
  padding: 5px 15px;
`;
 
export const BalanceContainer = styled.View`
  flex:1;
  align-items: flex-end;
`;
 
export const Balance = styled.Text`
  color: ${colors.WHITE};
  font-size:18px;
`
 
export const BalanceTitle = styled.Text`
  color: ${colors.WHITE};
  font-size:16px;
  
`;

src/components/Wallets.js

import React, { useState, useEffect } from 'react'
import * as UI from '../ui/Wallets.ui'
import { useFetch } from '../hooks/useFetch'
import WalletItem from './WalletItem'
 
export default function Wallets(){
  const [wallets, setWallets] = useState([])
 
  useEffect(() => {
    (async () => {
      const { data } = await useFetch('/wallets');
      setWallets(data)
    })()
 
  }, [])
  return (
    <UI.Container horizontal={true}  showsHorizontalScrollIndicator={false} decelerationRate="fast">
      {wallets.map((wallet) => (
        <WalletItem key={wallet.id} {...wallet}/>
      ))}
    </UI.Container>
  )
}

src/ui/Wallets.ui.js

import styled from 'styled-components/native';
import { Dimensions } from 'react-native';
 
const screenY = Dimensions.get('window').height;
 
export const Container = styled.ScrollView`
  max-height: ${screenY/6}px;
  margin:12px -20px 0px -20px;
`;

src/components/WalletItem.js

import React from 'react';
import * as UI from '../ui/WalletItem.ui';
export default function WalletItem({
  name,
  account,
  agency,
  digit,
  balance
}){
  return(
    <UI.Container>
      <UI.BankTitle>{name}</UI.BankTitle>
      <UI.BankInfo>
        <UI.BankInfoAccount>Conta: {account}-{digit}</UI.BankInfoAccount>
        <UI.BankInfoAccount>Agência: {agency}</UI.BankInfoAccount>
        <UI.BankInfoBalance>{balance.money()}</UI.BankInfoBalance>
      </UI.BankInfo>
    </UI.Container>
  )
}

src/ui/WalletItem.ui.js

import styled from 'styled-components/native';
import { Dimensions } from 'react-native';
import colors from '../system/colors';
 
export const Container = styled.View`
  height: 100px;
  width: 120px;
  margin: 0 15px;
  border-radius: 15px;
  padding:10px 15px;
  background-color: ${colors.WHITE};
  shadow-color: #323232;
  shadow-offset: 0 0;
  shadow-opacity: .5;
  shadow-radius: 12px;
  elevation: 5;
`;
 
export const BankTitle = styled.Text`
  font-size: 12px;
  color: ${colors.BLUE};
  font-weight:bold;
`;
 
export const BankInfo = styled.View``;
 
export const BankInfoAccount = styled.Text`
  font-size: 11px;
  color: ${colors.ORANGE}
`;
 
export const BankInfoBalance = styled.Text`
  padding-top:5px;
  font-size:12px;
  color: ${colors.BLUE};
  font-weight:bold;
`;

src/components/Transactions.js

import React, { useState, useEffect } from 'react';
import * as UI from '../ui/Transactions.ui'
import { useFetch } from '../hooks/useFetch';
import TransactionItem from './TransactionItem';
 
export default function Transactions(){
  const [transactions, setTransactions] = useState([]);
 
  const fetchTransactions = async () => {
    const { data } = await useFetch('/transactions');
    setTransactions(data)
 
  }
  useEffect(() => {
    fetchTransactions()
  },[])
 
  return (
    <UI.TransactionList
      data={transactions}
      renderItem={ TransactionItem }
      keyExtractor={ item => item.id }
    />
  )
}

src/ui/Transactions.ui.js

import styled from 'styled-components/native'
import { Dimensions } from 'react-native';
 
const height = Dimensions.get('window').height;
export const TransactionList = styled.FlatList`
  padding-top:20px;
  max-height:${height - ((height/2) - 20)}px;
`;

src/components/TransactionItem.js

import React from 'react';
import * as UI from '../ui/TransactionItem.ui'
export default function TransactionItem({ item }){
  return (
    <UI.Container>
      <UI.ContainerHeader>
        <UI.Title>{item.name}</UI.Title>
        <UI.CostTitle>{item.cost.money()}</UI.CostTitle>
      </UI.ContainerHeader>
    </UI.Container>
  )
}

src/ui/TransactionItem.ui.js

import styled from 'styled-components/native'
import colors from '../system/colors';
 
export const Container = styled.View`
  padding:15px 15px 15px 15px;
  background-color: ${colors.WHITE};
  margin:5px 0;
`;
 
export const Title = styled.Text`
  color: ${colors.MIDNIGHT_BLUE};
  width: 75%;
  font-size:15px;
`
 
export const CostTitle = styled.Text`
  font-size:15px;
`;
 
export const ContainerHeader = styled.View`
  flex-direction:row;
  justify-content: space-between;
  width: 100%;
`;

src/ui/Dashboard.ui.js

import styled from 'styled-components/native';
import { Dimensions } from 'react-native';
import colors from '../system/colors';
 
const height = Dimensions.get('window').height;
 
export const Container = styled.View`
  position:relative;
  flex:1;
  width:100%;
  z-index:5;
  top: ${(height/4) - 50}px;
  padding:0 20px;
`
 
export const Title = styled.Text`
  font-size: 18px;
  color: ${colors.WHITE};
  font-weight: bold;
`

Agora que criamos todos nossos arquivos e toda estrutura necessária para nosso app, iremos importar alguns módulos, adicionar as rotas e nossa primeira tela.

Rode o seguinte comando:

npm install react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view @react-navigation/native @react-navigation/stack

Isso irá instalar toda parte de navigation no seu projeto. Após isso, vamos partir para o resto do desenvolvimento. Criaremos o arquivo de rota e a primeira página

src/system/routes.js

import React from 'react'
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Dashboard from '../pages/Dashboard';
import Header from '../components/Header';
import colors from './colors';
 
const Stack = createStackNavigator();
 
let navigationOptionDefault = {
  headerShown: true,
  header: () => <Header/>,
    headerMode: 'screen',
    cardStyle: { backgroundColor: colors.CLOUDS },
}
 
export default function Routes(){
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Dashboard" component={Dashboard} options={navigationOptionDefault}/>
      </Stack.Navigator>
    </NavigationContainer>
  )
}

src/pages/Dashboard.js

import React from 'react';
import * as UI from '../ui/Dashboard.ui'
import Wallets from '../components/Wallets';
import Transactions from '../components/Transactions';
import colors from '../system/colors';
 
export default function Dashboard(){
 
  return (
    <UI.Container>
      <UI.Title>Minhas Carteiras</UI.Title>
      <Wallets/>
      <UI.Title style={{color: colors.MIDNIGHT_BLUE, fontWeight: 'bold'}}>Contas Próximas</UI.Title>
      <Transactions/>
    </UI.Container>
  )
}

Neste momento nosso projeto está pronto! 

Podemos testar utilizando o seguinte comando:

expo start

E quando o METRO abrir

clique para executar no android, se o emulador estiver ligado ele vai compilar e exibir, ou você também pode baixar o expo para seu celular e fazer a leitura do código de barra que o metrô gera!

Por hoje é isso pessoal. Espero que minhas dicas ajudem bastante na sua jornada como dev na difícil etapa de tirar a ideia da mente e tornar em algo funcional!

Repositório do projeto: https://github.com/aylonMuramatsu/artigo-ui