Por: Theo Mendes < [email protected] >.
- 1. Contúdo
- 2. O Projeto
- 3. Significado dos logs
- 4. Instalação
- 5. Design
- 6. Toque a mais
- 6.1. Configurei o projeto de maneira segura para diversos ambientes, Dev, AdHoc e AppStore.
- 6.2. Exibo além do preço atual, o desconto atual do local.
- 6.3. Dentro das seções são ordanadas pela porcentagem do desconto
- 6.4. Uso células diferenciadas para o Hotel e Pacotes
- 6.5 Feed centrado na experiência do usuário
- 6.6 Auto Layout, a célula de pacote se adapita a largura do celular, mantendo aspect ratio.
- 6.7 Localização do app tanto em Inglês quanto Português.
- 7. Testes
- 8. Dependências
- 9. Vulnerabilidades
Arquitetura: MVVM-C
Aviso: Ao olhar o historico de commits poderá ver que tem um "outro" contribuidor no projeto, isso se deve ao fato que sem querer ao codar em um outro computador meu, esqueci de verificar o config do git, logo esses 3 commits ficaram como autoria da minha conta antiga no GitHub, na qual não tenho mais acesso.
A escolha resta arquitetura deve-se ao fato de ser bastante testável mas ainda ter um nível de simplicidade que pode ser rapidamente estruturado. Implementei um Coordinator pois isso é uma das coisas que falta no MVVM uma camada de routing.
O projeto é separado em 3 Schemas: Debug, AdHoc e AppStore e 3 Build Configurations: Development, Production, Stage, tem uma bundle id, nome e ícones diferentes para cada Build Configuration, para assim em um único aparelho poder se instaladas cada versão, o nome e ícone são para mera diferenciação visual.
O projeto para evitar erro humano automaticamente detecta para qual schema esta buildando e pega as variáveis de ambientes de acordo, por exemplo você pode querer durante o desenvolvimento usar o localhost como url para consumir uma API, e uma url real para o app em produção. Para assim evitar que haja chance de acabar indo uma url de desenvolvimento para produção.
Usei tanto o CocoaPods, como Carthage. Fiz essa escolha pois como o Carthage compila os frameworks quando você pega eles, isso faz diminuir consideravelmente o tempo de compilação do projeto todo, assim aumentando a produtividade. Usei o CocoaPods somente pelo fato que nem tudo esta no Carthage, principalmente dois grandes frameworks muito usados: SwiftLint e SwiftGen.
Resolvi não implementar tela de detalhe de um produto por questão de escopo, resolvi focar na organização do feed e do projeto.
Para realização de logs eu utilizei o os.log assim fica mais fácil de visualizar eles em ferramentas de analise de log, eu abri uma issue perguntando sobre o log da resposta da api, mas como não obtive resposta até o termino do desafio, fiz a escolha de imprimir no console.
- 👶 -> Init
- ⚰️ -> Deinit
- 🧠 -> View Model
- 🧭 -> Coordinator
- 🎮 -> View Controller
- 🔲 -> Table View Cell
- 🏻 -> Collection View Cell
- 📶 -> Network
- ⬇️ -> Receiving
- ⬆️ -> Sending
- ✅ -> Status Successful
⚠️ -> Status Failure
Para rodar este projeto você irá precisar do Ruby Bundler, Carthage e CocoaPods
Carthage pode ser instalado rodando o seguinte comando:
brew update
brew install carthage
O Bundle deve ter vindo junto com a instalação do Ruby. Você precisa do Ruby maior que 2.4. Se você não tem o bundle, pode ser instalado rodando o seguinte comando:
sudo gem install bundler
- Na pasta Root do projeto rode
bundle install
- Rode a instalação do CocoaPods
bundle exec pod install
- Rode o Carthage
carthage bootstrap --platform ios --cache-builds --no-use-binaries
Como o desafio não impunha um design especifico do feed, preparei algumas telas. Não quis seguir com o exemplo de tabulação dado no README do desafio pelo de ter ja de cara um problema de UX, o usuário para ir até a informação que precisa precisa escrolar muito, como por exemplo, se ele quiser procurar um hotel 3 estrelas, na tabulação de exemplo ele teria que percorrer um grande caminho e poderia até desistir da compra.
Analisando o app de vocês para iOS e o site, para mim ficou claro que vocês valorizam dar destaque para o local e o preço atrativo. Seguindo esta ideia resolvi aplicar um feed com sessões laterais, dentro das sessões laterais eu ordenei decrescente pela porcentagem de desconto, logo os hotéis que acabaram de ter desconto ficam por ultimo.
Percebi que a Hurb da bastante valor para os pacotes, levando em conta isso resolvi colocar os pacotes como a primeira coisa do feed e dando bastante destaque para foto.
Preparei também 2 telas extras, uma de carregando e outra de quando deu erro, por questão de escopo não preparei métodos para se recuperar do erro.
Ao invés de implementar telas extras resolvi somente focar no desenvolvimento do feed, para assim criar um feed bem conceituado e um projeto bem configurado
Isso mantem a consistência do projeto entre os colaboradores, não precisando ficar mudando strings de API antes de dar um push, por exemplo, ou até mesmo ao mandar para a AppStore.
Pela análise tanto do app como site vi que uma coisa que vocês valorizam é o preço atrativo, então adicionar essa lógica me pareceu interessante.
Com isso eu crio um destaque para os maiores descontos, e consequentemente o item que o desconto acabou de finalizar tem menos prioridade no feed.
A Hurb valoriza os seus pacotes, então colocando eles na primeira parte do feed, e com elementos diferenciados do hotel, crio um destaque para eles.
O usuário acha rapidamente a informação que ele quer, como o Preço, facilidades, local e estrelas.
A célula fica proporcional para qualquer dispositivo, e as de hotel sempre ficam no centro da tela.
Como a Hurb esta em um processo de internacionalização, é uma obrigação do app se adaptar aos locais que ele vai está disponível. Isso incluindo o preço, como por exemplo em en_US 3,000.00 em pt_BR 3.000,00. Mas tomei cuidado com o fato da API retornar a currency que o preço esta, então independente do caso vai sempre aparecer o símbolo como R$.
Total de cobertura: ~88% pelo XCode
Criei testes unitários para o ViewModel e o Network layer, usei um stub em formato json para mockar a resposta da API, para assim garantir a consistência dos dados. o stub criei de uma resposta da API da Hurb, mas deletei alguns objetos para garantir a diferença entre a API real para o stub (So para fim de testes de objetos retornados).
Pelo fato do app conter uma tela eu testei o scroll vertical, e swipe em duas sessões.
8.1 Bundler
Uso ele para garantir consistência entre as versões do cocoapods dos contribuidores do projeto, assim evitando problemas no Podfile.lock e consequentemente no workspace.
8.2. CocoaPods
8.2.1. SwiftLint
Creio que em grandes projetos tem que haver uma maneira de garantir que as regras de boas práticas sejam compridas, então não há escolha melhor que esta.
8.2.2. SwiftGen
Um dos grandes problemas, principalmente em assets, é garantir que eles sejam type-safe, escolhi ele para garantir que os Assets e arquivos de Localização fiquem todos type-safe.
8.3. Carthage
8.3.1. RxSwift
O forte do MVVM é a ligação entre a View e ViewModel, então bibliotecas reativas são muito bem vindas. Eu fiquei em duvida entre usar ele ou ReactiveSwift, ReactiveSwift esta presente a mais tempo no mundo Reativo do Swift e ele se aproxima mais a guidelines do Swift do que resto do mundo, por outro lado RxSwift se aproxima ao framework ReactiveX que esta disponível em diversas plataformas logo tenta manter uma guideline padrão, creio que em um lugar como a Hurb com diversas linguagens de desenvolvimento seja mais atrativo manter um padrão entre elas.
8.3.2. RxDataSources
Uma falha no RxSwift é que ele não facilita a construção de feeds mais complexos que simples colections views ou table views, com isso para facilitar o trabalho resolvi usar ele.
8.3.3. Moya
Uma das grandes dificuldades em network layer é garantir que ele seja altamente testável, escolhi o Moya por ele ser construído em cima do Alamofire, que ja esta a muito tempo presente no Swift, ser altamente testável e type-safe.
8.3.4. SnapKit
Ficar escrevendo constraints é um trabalho que demanda um certo tempo, e de não ser muito bonito em escrever, SnapKit facilita bastante mas ainda se assemelha muito a lógica da constraint nativa.
8.3.5. Kingfisher
Kingfisher oferece maneiras eficientes de fazer o download de imagens e guardar em cache, evitando que a pessoa tenha sempre que fazer download de imagem, assim aumentando a responsividade do app e economizando gets na AWS ou outro serviço de hospedagem, o que significa menos $$ gasto.
Não sei se é considerado uma vulnerabilidade, mas pelo fato de estar usando Swift 5.1 o Moya não atualizou certas dependências dele na versão estavel, tal como o ReactiveSwift que ainda esta na versão anterior assim não compilando. Com isso tive que usar a versão 14 Beta 4 do Moya.
No HotelCollectionViewCell.swift e PackageCollectionViewCell.swift tive que forçar o uso de HTTPS no link das imagens, ja que alguns domínios vinham da API usando protocolo HTTP, nos casos que analisei que não vinham como HTTPS, vinham com o domínio: omnibees.com, fiz uma checagem e vi que ele era compatível com HTTPS, logo por questões que a própria Apple impõe em aplicativos de produção, tive que forçar o HTTPs, mas como não conheço a API da Hurb não posso afirmar com certeza que todos os links vem da omnibees ou AWS, logo pode haver casos que a imagem não apareça no feed.