Android CWB, iniciando projeto! [Modulos]

Carlos
12 min readSep 10, 2019

--

Bom vamos por o conhecimento em prática. Vou começar a montar um aplicativo do zero, ele é bem simples (é, bem simples… sempre é bem simples não?) Então, o aplicativo vai ser da seguinte maneira. Um login, para acessar sua conta, e ai você verá o time Android! O que é o time Android? Vai ser um aplicativo que mostra os resultados, posições, estatisticas de cada sabor do android até hoje. Você vai ter que imaginar que cada versão do Android é um jogador de futebol! Sim, isso mesmo. Dessa maneira, consigo colocar imagens, informações, listar jogadores, criar todo um projeto. Eu não vou colocar toda a estrutura aqui das telas e etc de protótipo pois o projeto não tem um fim. Grandes projetos não tendem a terminar, estão sempre colocando novas funcionalidades em seu app que só conseguiram fazer no seus sites.

O link esta dividido em diferentes branchs, a parte 1 segue aqui:
https://github.com/nicconicco/AndroidCWB/tree/parte_1

Então vamos lá!

Abrindo o Android Studio e clicando em criar um emulador, você clicando em other images conseguirá ver todas as versões que o Android já lançou até hoje.

Android 3 Cupcake já esta deprecado.
Os símbolos do Android foram evoluindo.
Até o momento deste post a Oreo era a mais atual e a Q estava em beta.

Estes carinhas então irão ser nossos jogadores de futebol. Uns vão ser atacantes, outros meio campistas, defesa e goleiro (Sim eu gosto de futebol)

Além da posição deles, eles irão ter uma descrição do Sistema operacional deles (Isto vai ser feito pois quero me tornar um Google Developer Expert então preciso conhecer tudo desde os primórdios)

Além disso, eles vão ter jogos contra outros sistemas operacionais. Imaginem qual será seu adversário né? Para não gerar atrito de gostos, só será colocado Time Android 10 x 0 time adversário hehehehe.

Dado esse contexto do aplicativo. Vamos criar o nosso projeto: Android CWB.

[ Estou usando um macbook ]

File -> New -> New Project -> Empty Activity -> Name: AndroidCWB e o resto se coloca o package que ficar a vontade e escolha o lugar onde criar o projeto. Deixa o gradle sincronizar e baixar as dependências do projeto.

Assim, eu as vezes uso a visão Android de projeto lá na esquerda superior e também a visão de projeto, clicando na flecha ali para mudar e ficar da seguinte maneira:

Vou deixar por enquanto na visão Android.

Depois de criado o projeto, a primeira tela como tinha dito será a de login. Para podermos ser mais rápidos e criarmos uma arquitetura legal para o APP vou mockar informações. Por exemplo. Quando fizer uma requisição ao meu servidor, ele será a princípio offline. Seria como uma falsa chamada. Com dados estáticos. Por quê disso? Primeiro pois ainda estou pensando em qual serviço vou usar, hoje temos o Firebase da google, Back4apps, entre outras maneiras. Então como quero mostrar na maneira de desenvolver, estou não ficando preso a um framework, posso fazer offline e depois mudar conforme o meu gosto. Isso é uma maneira de organizar seu código. Quanto maior liberdade para mudanças sem afetar seu software você se planeja, melhor e mais seguro fica de dar manutenção. Para isso, vamos implementar vários testes unitários seguindo o critério de 70% de testes unitários 20% de testes integrados e 10% de testes automatizados. Para essa métrica estou levando em conta o custo de tempo para o App. Como disse anteriormente, o app não tem fim então muitas ideias vão surgir no caminho. Ainda quero ver de implementar o mesmo código em várias arquiteturas diferentes e mostrar que é possível essa mudança e aí teríamos uma métrica de quanto código foi escrito e quais foram as dificuldades encontradas.

Vamos criar nossa primeira tela, e fica tranquilo se alguma coisa você não entender, no fim do capitulo vai ter o link no meu github mostrando o código funcionando.

Para isso, e para no futuro nosso código estar bem organizado, vou seguir com o seguinte pensamento. Um presentation (Apresentação), o framework separado e o Core de nossa aplicação. Logo você vai entender melhor.

A estrutura do código para o presentation e o framework ficará assim (Mais tarde veremos o Core = Coração)

clicando no meu pacote, New -> Package -> presentation / framework

Vamos criar o Modulo do Core também, esta parte é separada do App pois não depende das telinhas que nosso app esta. Ali estaria o código de use cases (Caso de us) , Domain (Classes) e Data (Como deve-se acessar os dados)

Então no app clicando com o botão direito, New -> Module -> Java Library -> Library name: core. Espera o gradle sincronizar. Ai abre o Gradle Script e seleciona o Module: app. (Temos o gradle do app = seu aplicativo e o gradle do projeto Project: AndroidCWB. Digamos para pessoas leigas que esse é o maiorzão, o que gerencia as dependências maiores do projeto)

Abrindo o Module: app, colocamos lá nas depedencies

implementation project(':core')

E sincronizamos.

Ok, agora vamos criar nossa primeira activitie, o login. Para não ficar um package extendido vou criar o package login e o home (tela principal do app)

E ai sim, New -> Activity -> Empty Activity -> LoginActivity

Eu uso o option + enter para verificar se aconteceu algum erro. O import do meu Android Studio as vezes buga.

clique no R.layout.activity_login e cole este xml de layout do nosso login.

Ele usa o ConstraintLayout, que é como a Google esta sugerindo fazer nossos novos layouts por questão de performace. É bem simples a ideia, você coloca ids em seus blocos, e diz se a orientação dele vai para o pai (parent) ou para o id que você adicionou como endereço. Em questão de gosto, eu gostava muito do LinearLayout anyway, vamos seguir da melhor maneira possível.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
>

<EditText
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"

android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"

android:hint="@string/prompt_password"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username"
/>

<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="48dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="48dp"
android:layout_marginBottom="64dp"
android:text="@string/action_sign_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password"
app:layout_constraintVertical_bias="0.2"/>

<ProgressBar
android:id="@+id/loading"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="32dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="64dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/password"
app:layout_constraintStart_toStartOf="@+id/password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Provavelmente deu alguns pontos vermelhos como no "@dimen/activity_vertical_margin” só clicar option + enter e criar a distância. Seguindo a tona do material design da google, 16dp.

Nos textos coloquei Email, Password, Sign in, Sign in.

Vai ficar assim no Preview do Android Studio.

Ok, agora vamos a parte radical hehe. Para nosso primeiro app, e para ficar fácil e claro de testar, vou usar a arquitetura MVP com interactor e repositorios, logo ficará claro como tudo se une.

Mas antes disso, para não termos memory leak no nosso projeto vamos montar uma base dos nossos presenters.

Para isso, vamos criar um modulo chamado utils (Razão a qual esse modulo não é um utils para todas as arquiteturas, ela será apenas para o mvp)

New -> Module -> Java Library -> utils

Assim como antes, adicionamos ela no nosso module: app. e sincronizamos

Antes que venha algum tipo de opinião, ei, mas isto não seria overengineering?

Não, não é pois depois eu vou criar o mesmo projeto em outra branch só que com o utils do mvvm, mvc, etc. Não faz sentido essas arquiteturas terem esses códigos perdidos por ai no seu software.

Anyway,

Lá no Module: utils para rodar certinho, o código ficou o seguinte:

apply plugin: 'java-library'
apply plugin: 'kotlin'

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
}

sourceCompatibility = "7"
targetCompatibility = "7"

A nossa BasePresenter ficou assim:

package carlos.nicolau.galves.utils

import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.Lifecycle


abstract class BasePresenter <V: Contract.View>: Contract.Presenter<V> {

protected var mView: V? = null

override fun attach(view: V) {
this.mView = view
view.getLifecycle()?.addObserver(this)
}

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
override fun detach() {
this.mView = null
}
}

E nosso contrato:

package carlos.nicolau.galves.utils

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent

interface Contract {
interface Presenter<V : View> : LifecycleObserver {
fun attach(view: V)

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun detach()
}

interface View {
fun getLifecycle(): Lifecycle?
}
}

Eu deletei o MyClass inicial, pode tirar do Modulo do core também não vamos usar.

Ficou assim:

Bom, agora que temos configurado. Vamos criar nosso primeiro presenter. O LoginPresenter.

Lá na pasta de login, New -> Kotlin File -> LoginPresenter, selecione interface no segundo campo.

E o mesmo para a implementação: New -> Kotlin File -> LoginPresenterImpl mas agora selecione class no segundo campo. (Alguns chamam de Contrato, outros pelo nome. Fique a vontade para escolher seu padrão, aqui usarei a mesma nomenclatura para ficar o mais claro possível. Um, é o LoginPresenter que seria o osso, o outro é a implementação dele)

Todo Presenter, vai ter a seguinte estrutura,

package carlos.nicolau.galves.androidcwb.presentation.login

import carlos.nicolau.galves.utils.Contract

interface LoginPresenter {
interface View: Contract.View{
}

interface Presenter: Contract.Presenter<View> {
}
}

Para nossa comodidade, no todo (a fazer) do futuro eu vou criar um template que crie o presenter com o nome solicitado com toda sua herança já estruturada, aqui você precisa escrever ou copiar para ao menos saber o que ta acontecendo.

Nosso LoginPresenterImpl ficaria assim

package carlos.nicolau.galves.androidcwb.presentation.login

import carlos.nicolau.galves.utils.BasePresenter

class LoginPresenterImpl : BasePresenter<LoginPresenter.View>(), LoginPresenter.Presenter {

}

Ok, estamos quase terminando, ainda vamos escrever ao menos um teste para você visualizar.

Nossa estrutura ficou assim:

Agora, vamos lá para nosso LoginActivity

Vamos mudar nossa MainActivity de entrada para o LoginActivity no AndroidManifest. E também pode deletar a MainActivity e o activity_main.xml.

Para encontrar eles, use shift + shift e digite os nomes para encontrar as classes.

Clique no botão Scroll from source no canto esquerdo superior e clique nele, ele vai te guiar ate o arquivo para deletar.

Pode por, delete anyway, eu deletei o .xml primeiro e depois a MainActivity.

Ok,

Agora nosso LoginActivity é a tela inicial. Se quiser rodar o emulador, você vai ver algo assim:

ps: Para conhecimento, no primeiro Run, aquele botão de rodar o app. Caso não saiba, esta ali no centro. Run App é a mensagem. Ele gera uma pasta java/generated que mostra como esta seu BuildConfig. Esse BuildConfig mais além vamos mecher nele, para o flavor entre outras coisas. Segue uma imagem do que gerou, faz sentido né? Lembra dessa imagem.

Continuando, Vamos fazer o primeiro passo, digitar um email, uma senha, ai o presenter que é encarregado de pegar essa informação e enviar para algum lado. Isso que vamos ver a seguir se chama responsabilidade que cada camada tem. Isso é importante no código e no projeto por questão de organização e onde entramos também na orientação a objeto, diminuir o acoplamento (muitos métodos dentro de uma classe) e aumentando a coesão (A view, deve apenas pegar eventos e disparar para outros lugar, ela não precisa ter ideia do que irá acontecer com isso)

Claro que, como dito também, vamos usar para as ações do usuário, o interactor, neste caso o click do presenter, vai ativar a ação do use case de fazer login. Desta maneira é legal pois depois essa interação pode ser usada em outro lugar sem depender de qual presenter esta sendo chamado, apenas tem que se instanciar ele. São ações já bem definidas.

Então vamos lá, coloque uma instancia do LoginPresenter no LoginActivity, e implemente os contratos do LoginPresenter.View também.

Ficou assim, ainda não olhando o UseCase

Agora vamos criar 3 pacotes para o Core, finalmente, O pacote Data, Domain e Interactors.

Vamos criar o click na view do login, para enviar o email e o password para o presenter.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)

setupView()
}

private fun setupView() {

loginPresenterImpl = LoginPresenterImpl()
loginPresenterImpl.attach(this)

login.setOnClickListener {
loginPresenterImpl.onClickBtnLogin(username.text.toString(), password.text.toString())
}
}

Eu criei um setupView, que é para o estado inicial da minha tela. Recomendo que use o mesmo, fica muito estranho as vezes olhar projeto e olhar, setupView, configView, defina um nome e segue ele, não mude a nomenclatura, isso atrapalha depois em entender se realmente é isso que se espera que faça.

Uma questão, aqui que me levantaram já, é, a view esta decidindo qual método chamar do presenter. Mas não o vejo assim. O setOnClickListener é um evento, poderia colocar ele em um Interactor de ação e não de use case mas ai sim acho que fica muito overengenering, o Click é uma ação do Button, o onClickBtnLogin do presenter é apenas uma chamada de método, que para clarear nosso código segue a mesma regra de nomenclatura do onClickBtn.

A view, ela não tem ideia do que tá chamando lá dentro, ela fez sua ação, Click. E é isso.

Agora dentro do presenter, vejamos como fica:

Aqui, como queremos usar o UseCase. Vamos pensar, vamos fazer o login, ele deve retornar um Usuário com o campo de conseguiu fazer o Login? Sim, Não. Então na verdade, estamos em questão, não fazendo o Login e sim procurando informação do Usuário. Nosso UseCase se chamará GetUserUseCase. Evite fazer UseCase com muitas ações, seguindo a ideia dos conceitos do SOLID, melhor mais interfaces do que muitas funcionalidades sem necessidade de determinados métodos terem essa visão. SRP para saber mais.

Criei então o GetUserUseCase uma interface para ele e criei a classe User, e alterei o construtor do LoginPresenter, ah, e esqueci de comentar mas é bom sempre que você fizer uma chamada a um "serviço da internet" você mostrar um progress dialog para mostrar a seu usuário que você esta carregando uma informação. Para isso, a view do contrato do Login tem que mostrar isso. Alterei o LoginPresenter.View, talvez depois a gente veja que esse fator se repete e podemos subir a hierarquia desse método para o Contract.View mas veremos isso quando acontecer.

Classes modificadas:

Chegamos a parte em que temos que começar a validar nosso presenter. Por exemplo. Ao fazer a chamada de Login, deve acontecer sempre o Login verdadeiro ao que definimos. Vamos verificar? Para isto vamos usar o poder dos testes unitários. E vamos usar o Mockito. Por quê? Por que é legal, confere ai.

ps: Tive um problema de cache com o Android Studio, ele não tava localizando o :core. Para isso, verifiquei que precisa colocar a extensão Kotlin também no core.

Module: core

apply plugin: 'java-library'
apply plugin: 'kotlin'

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
}

sourceCompatibility = "7"
targetCompatibility = "7"

Assim o teste passa.

Esta parte foi revisada por Rodrigo Cericatto, Obrigado!

Para validar o cenário completo tive que mudar o meu LoginPresenterImpl colocando o progress no começo da chamada e no final escondendo-o.

Ficou assim para você conseguir ver o teste passar:

class LoginPresenterImpl(
private val getUserUserCase: GetUserUseCase
)
: BasePresenter<LoginPresenter.View>(), LoginPresenter.Presenter {

override fun onClickButtonLogin(username: String, password: String) {
mView?.showLoading()
val user = getUserUserCase.execute(username, password)
mView?.hideLoading()
}
}

E o teste é o seguinte:

Ps: Cuidado na validação do execute, no primeiro ele esta dentro e depois fora.(Linhas 29 e 33 acima)

O teste unitário ele é bem simples, você ainda seta comportamentos então a pergunta então por quê é importante isso? Justamente por isso, para manter o controle do simples. Imagine você ter um problema com o seu backend e ai ele dizer para você, seu código ta errado. As vezes pode ser mas é importante você não ter só "la garantia soy yo" então essa é minha visão de importancia.

Fica fácil pegar algo que mudei se você der coverage do seu código. Depois vamos configurar um CI, com analisador de código mas isso lá depois. E bom, para terminar, segue o exemplo do espresso, para verificarmos o estado inicial da tela.

O teste é simples mas é importante.

Ficamos por aqui, nos próximos episódios vou acrescentando mais testes, também vamos colocar injeção de dependência. Testes integrados, criar nosso repository, etc.

o repositório de donwload eu vou subir agora então provavelmente vai estar aqui:

https://github.com/nicconicco/AndroidCWB

Obrigado pela leitura espero que eu possa te ajudar a evoluir em sua carreira.

Att, Carlos Nicolau Galves.

Parte 2 do : https://medium.com/@nicolaugalves/android-cwb-parte-2-fbf44ef004cf

--

--