App para a loja fictícia Stonesafio. Solução desenvolvida por Allan Yoshio Hasegawa para o Desafio Stone (README original abaixo).
Demo time:
O código está organizado usando a Clean Architecture. A implementação foi fortemente baseada nos artigos e projetos do Fernando Cejas:
- Artigo: https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/
- Projeto: https://github.com/android10/Android-CleanArchitecture
Esse projeto esta dividido em três layers e quatro módulos:
- Outer layer: contém os módulos
app
edata
- Mid layer: contém o módulo
presentation
- Inner layer: contém o módulo
domain
As mesmas regras da Clean Architecture são aplicadas para essas layers. As dependências seguem apenas uma direção, da outer layer para a inner layer.
Uma breve descrição:
domain
: Regras de negócios e as interfaces de como interagir com serviços e repositórios.presentation
: Lógica que conecta as regras de negócios com a UI. Manipulação dos dados para visualização.data
: Implementações dos serviços e repositórios.app
: Implementações das "Views" do MVI e dosNavigator
s. Implementação específica para a plataforma Android que não se encaixam no módulodata
(por exemplo, logging).
O módulo domain
está organizado por features. Uma feature pode conter:
- Interfaces para serviços e repositórios
- Models, que são entities usadas para transferência de dados; sem lógica
- UseCases que fazem a implementação das regras de negócio
O módulo domain
não depende de nenhum outro módulo.
Depende apenas do módulo domain
. É organizado por telas, ou screens. As telas usam o padrão
de arquitetura MVI (Model-View-Intents). A implementação foi baseada na série fantástica
de Hannes Dorfmann: http://hannesdorfmann.com/android/mosby3-mvi-1
Cada tela tem um contrato, ou contract, com os elementos:
View
: interface que define os intents daView
, como clicks em botões ou scrolls da tela. AView
é passiva e não contém lógica.ViewState
: Descreve os possíveis estados em que umaView
pode se encontrar.StateEvent
: Eventos que podem alterar oViewState
.Navigator
: Opções de navegação da tela.
Além do contrato, cada tela implementa o Presenter
. O Presenter
é quem orchestra os intents da
View
com os UseCases do módulo domain
para construir o ViewState
. O ViewState
é construído
usando StateEvent
s e o padrão do state reducer.
Como a lógica do Presenter pode ficar muito grande, ele delega parte de sua lógica para UseCases.
Diferentemente dos UseCases do domain
, esses UseCases retornam apenas StateEvent
's.
Assim, esses UseCases acabam servindo também como data mappers encontrado em algumas
implementações de Clean Architecture.
O módulo data
faz a implementação dos serviços e repositórios definidos no módulo domain
.
A organização é feita por feature, assim como no módulo domain
.
O módulo app
está organizdo por telas e features. Sua principal função é a implementação da
View
do MVI. Ou seja, é nela onde a maior parte da interação do app com a Android Framework
acontece. Assim, essa layer lida com a Activity
, RecyclerView
, etc, coisas que as outras
layers nem sabem que existem.
Cada tela tem um Controller
e um Navigator
. O navigator implementa a lógica para a navegação
entre as telas. O Controller
é algo parecido com um Fragment
, porém usando a biblioteca
Conductor. O Controller
faz a implementação da
View
do MVI.
Além disso, o Controller
faz a criação do Presenter
. Dependency Injection é usado para
facilitar a gerência das dependências.
Uma alternativa famosa é a MVP. O problema da MVP é que é difícil gerenciar o estado da View
.
Essa problema é ainda mais grave em uma arquitetura completamente reativa, onde eventos podem
ser emitidos a qualquer momento e em ordens inesperadas. Com isso, a View
pode entrar em um
estado inválido e difícil de ser replicado.
Esse problema é mais fácil de resolver com a MVI, onde o estado da View
é descrito e bem
definido. As ações que mudam o estado da View
podem ser salvas (e até mesmo enviadas junto com
o crash report) e repetidas para replicar uma falha.
Além disso, a MVI facilita a recuperação do estado da View
quando se tem uma
"configuration change".
Esse blog post resume bem: https://academy.realm.io/posts/michael-yotive-state-of-fragments-2017/?
Conductor é uma alternativa para a bagunça que são os Fragments. Lançado em uma época onde todo mundo odiava os Fragments, o Conductor chegou como um salvador. Hoje eu gosto de usar ele pela sua simplicidade, e até agora não senti falta dos Fragments.
Porém, hoje em dia Fragments não são tão ruins, só tem que usar com cuidado.
Aproveitei esse projeto para testar a MVI. Eu já conhecia a teoria por meio dos blog posts do Hannes Dorfmann, logo, optei por usar a biblioteca dela, a Mosby.
Acredito que essa organização é a primeira coisa que a gente ve em um projeto. A organização por features é simples de entender, e até uma pessoa que não conhece o projeto consegue navegar nele.
Essa é a separação proposta por Fernando Cejas em seu projeto demonstrando Clean Architecture: https://github.com/android10/Android-CleanArchitecture
Essa separação permite melhor organização das dependências.
Por exemplo, o módulo domain
é um modulo Kotlin normal, sem dependência com a Android Framework.
Além disso, ele só importa as dependências necessárias.
Isso evita que a parte do domain
acabe usando dependências erradas
(como algo da Android Framework, ou algum objeto do presentation
).
ou, "Não seria mais fácil reutilizar um único model em todas as layers?"
O problema é quando a View
acaba dependendo de um model do domain
. Esse model pode acabar
sendo usado em diversas partes da UI. Caso precise ser feito algum refatoramento nesse model,
todas as layers acabam sendo impactadas, principalmente a de UI. Com uma separação, a mudança é
feita em apenas um lugar.
"Allan, confirmei meu pagamento, o app mandou POST pra backend, porém, antes de salvar a transação, eu cliquei para voltar e a transação não foi salva. E agora?"
O problema é que o lifecycle da stream que faz essa operação está associada ao lifecycle da tela de pagamento. Ou seja, quando a tela de pagamento é fechada (o user clicka back button), a stream é unsubscribed.
Uma solução seria apenas aumentar o lifecycle da stream, deixando ela sobreviver a destruição da tela. Outra solução seria criar uma stream "singleton" com o mesmo lifecycle do app.
O ListingPresenter
teve a sorte de ganhar testes. Os outros não receberam por falta de tempo.
Como esse foi um projeto pequeno em escopo, e não pretendo refatorar ele, não me preocupei muito.
Mas testes são importantes o7
ReadMe Original abaixo.
O desafio consiste em criar uma loja de itens de Star Wars que o usuário é capaz de adicionar os itens desejados em um carrinho de compras e finalizar a compra com uma simulação de transação e-commerce.
O candidato deve dar fork neste repositório e após o termino do desenvolvimento, realizar um pull request para análise do time.
Para obter os itens da loja, sua aplicação deverá realizar uma chamada GET
na URL https://raw.githubusercontent.com/stone-pagamentos/desafio-mobile/master/products.json
A lista de itens deve exibir as seguintes informações:
- Nome [title]
- Preço [price]
- Vendedor [seller]
- Foto do item [thumbnailHd]
Após o usuário adicionar todos os itens no carrinho, ele deverá finalizar a compra. Para finalizar a compra, aconselhamos que use o Apiary como API nessa etapa.
Sua aplicação deve realizar um POST
com os seguintes atributos:
- Número do cartão (máximo de 16 números - XXXX XXXX XXXX XXXX)
- Nome do portador do cartão
- Vencimento do cartão (MM/yy)
- CVV (código encontrado na parte traseira do cartão)
- Valor da transação (total dos itens no carrinho)
{
"card_number":"1234123412341234",
"value":7990,
"cvv":789,
"card_holder_name":"Luke Skywalker",
"exp_date":"12/24"
}
Todas as transações realizadas devem ser salvas em um banco interno com os seguintes campos:
- Valor
- Data e hora
- Últimos 4 dígitos do cartão
- Nome do portador do cartão
A aplicação deverá conter uma tela para exibir as transações que foram salvas em seu banco de dados.
MIT License
Copyright (c) 2016 Stone Pagamentos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.