# Development Patterns
Esse documento foca na definição padrões de desenvolvimento android sob a arquitetura MVVM.
## Nomes
Baseado no Desenvolvimento Voltado ao Domínio (Domain Driven Development), temos como foco a linguagem de negócio.
### Android Class
>DomainType
- SomeActivty
- SomeFragment
- SomeView
- SomeAdapter
---
### Object names
>**Data**: DomainRemoteData(network) e DomainRemoteEntity(local)
- UserRemoteEntity
- UserEntity
>**Domain**: Nome puro
- User
>**View**
DomainUiModel
- UserUiModel
---
### Layout
>**Layout file name**
domain_type_layout.xml
- login_activity.xml
- onboarding_something_fragment.xml
- some_list_item
- some_view
>**View ID**
domainViewType
- Text Label: userNameLabel
- Text Value: userNameText
- Input Text: userNameInput
- Image: profileImage
>**Recycler View:**
- ViewId : domainList
- Layout name: domain_list_item
---
### Strings
>domain_function
- place_list_screen_title
- place_name_label
- place_name_hint
- place_not_found_error_message
---
### Packages
Pacotes sempre com nome em minúsculo. Máximo duas palavras.
- location
- remote.config
- compoundname.ui
---
### Navigator
**Actions:** fromClassNameToClassName
- fromSomeOriginToSomeDestiny - Quando se tem a origem explítica
- toSomeDesity - Quando se tem a origem implícita
Dessa maneira, quando obtemos o NavController para efetuar uma navegação temos a escrita mais semântica:
```kotlin
//quando se tem origem e destino
navController.navigate(R.id.fromSomeOriginToSomeDestiny)
//quando se tem apenas o destino
navController.navigate(R.id.toSomeDestiny)
```
## Packing by feature
Features verticais por si só não garantem um melhor isolamento mas ajudam na visualização das funções existentes no sistema alem de promover um desacoplamento no sentido modular, visto que a separação de pastas por feature é a primeira fase de uma modularização, se essa fase é feita durante todo o desenvolvimento, então se torna mais fácil a extração de determinada feature para um modulo quando se faz necessário. Você pode ver mais abordagens sobre o tema [aqui](https://proandroiddev.com/package-by-type-by-layer-by-feature-vs-package-by-layered-feature-e59921a4dffa)
>com.example
>>**injection**
>>>**feature.a**
>>>>data
>>>>
>>>>Injection
>>>>...
>>>>
>>>FeatureActivity.kt
>>>
>>>**feature.b**
>>>>data
>>>>
>>>>Injection
>>>>...
>>>>
>>>FeatureActivity.kt
>>>>
>>...
## Libs
Utilizar dependências estáveis
## Gradle
Variaveis de versão de lib no formato:
## Arquitetura
**MVVM (Model, View, ViewModel)**: arquitetura onde os dados são expostos à view por meio de streams(base dessa arquitetura).
### View
Views são responsáveis por exibir dados e **receber eventos** emitidos pela interação do usuário.
O ideal é que ela seja o mais passiva possível. Ela repassa as interações que recebe, para o View Model. E trata os estados vindos do View Model.
### ViewModel
O View model é um subordinado da View, responsável por **tratar eventos** emitidos pela interação do usuário com a tela e fazer o tratamento de dados a serem apresentados.
View model não deveria conhecer framework, a classe Context/ Application por exemplo.
### Business
Use cases descrevem formas de interação dos usuários com determinada feature(o que depende do nível de abstração que se deseja). Os métodos de objetos dessa camada devem fazer sentido para o negócio, mais uma vez seguindo as premissas do Domain Driven Design. Não é interessante ter métodos genericos como execute ou algo do tipo nessa camada, mas sim, métodos como, login... Não tem sentido usar classes base em interacor, use cases devem ser únicos e servir a algum propósito do negócio, independente do nível de abstração que se deseja seguir.
### Data
Objetos de dados do banco ou do back-end não deveriam ser repassados diretamente à camada de negócio, esses objetos de dados (DTO's) devem ser convertidos para os objetos de domínio, isolando a camada de negócio resguardando de possíveis alterações no backend.

:::info
É valido lembrar que no android, shared preference faz parte da camada de data, sendo um tipo de datasource.
:::
### View State
A view não pode tomar decisões por si só, também é interessante que ela tenha quase nenhuma ou nenhuma lógica. Nesse sentido, a view deve receber instruções do que deverá ser feito, como a visibilidade e estado(enabled/ disabled) de campos, apresentação de dialogos (mensagem, erro...) entre outras particularidades que dependem exclusivamente do contexto da view, como a navegação por meio do Navigation Component.
Nesse sentido, vamos distinguir o que é dado de view (UiModel) e o que é View Instruction, separando-os em distintos streams, já que dados de apresentação não necessariamente refletem um estado da view em si, onde um UiModel existe em mais de um estado da view.
```kotlin
class ViewModel{
private val _viewState = SingleLiveEvent<ViewInstruction>()
val viewState : LiveData<ViewInstruction> = _viewState
fun loadSomething(){
val result = somethingInteractor.loadSomething()
result.onSuccess{
_viewState.postValue(State.Success)
}
}
}
class SomeFragment : Fragment(){
//... view model and fragment configurations
setupObservers(){
viewModel.viewInstruction.observe(lifecycleOwner, Observer{state->
onViewInstruction(state)
})
}
onViewInstruction(viewState: ViewState){
when(instruction){
is Loading -> print("Block some views, show some load progress")
is Success -> print("Free some views, show some message")
is Failure -> print("Free some views, show some error message")
}
}
}
```
Exemplo completo [aqui](https://gist.github.com/roubertedgar/4d73299c1bb2fb2ea901283f9e552e65)
## Boas Praticas
:cry: **Não** devemos acessar diretamente um valor do ViewModel
``` kotlin
class SomeActivtiy : AppCompatActivity() {
onCreate() {
button.setOnClickListener{
val something = viewModel.getForm()
val someBoolean = viewModel.viewInstruction.value
}
}
}
```
:smile: Os dados sempre são expostos por meio de streammings
``` kotlin
class SomeActivtiy : AppCompatActivity() {
fun setupObservers() {
viewModel.viewInstruction.observe{ instruction ->
when(instruction) {
VALID_FORM = // todo: prosseguir no fluxo
INVALID_FORM = // todo: mostra msg de erro
}
}
}
}
```
:cry: **Não** é indiciado que a própria view controle seus estados, como cores, visibilidade...
``` kotlin
class SomeActivtiy : AppCompatActivity() {
fun setupObservers() {
button.setOnClickListener {
button.enabled = false
viewModel.doSomething()
}
}
}
```
:smile: É melhor que o ViewModel conte para view qual é seu estado.
```kotlin
fun setupObservers() {
viewModel.viewState.observe { viewState ->
when(viewState) {
READ_ONLY -> blockViews()
WRITE -> freeViews()
}
}
}
```
:sweat: Evitar lógica condicional (if, else) nas views
``` kotlin
class SomeActivtiy :AppCompatActivity() {
fun onBackPressed() {
if (viewModel.viewState.value == ViewState.SHOWING_DIALOG) {
dialog.kill()
} else {
navigator.navigateUp()
}
}
}
```
:smile: Deixe que o view model cuide dessas lógicas e faça a exposição do resultado por meio de um streamming.
```kotlin
class SomeActivtiy :AppCompatActivity() {
fun onBackPressed() { viewModel.onBackPressed() }
}
class ViewModel {
//streams -> view state...
fun onBackPressed() {
if (viewState.value == SHOWING_DIALOG){
viewState.postValue(WRITE)
} else {
viewState.postValue(FINAL_STATE)
}
}
}
```
## Base classes
Classes base, como **BaseActivity** e **BaseFragment** são de propósito geral e deveriam conter apenas informações básicas e comuns a todas as telas, assim como configurações... Nesse sentido, não deveriamos explicitar injeção de dependencias ou até mesmo definições de arquitetura, como por exemplo `BaseActivity<T:ViewModel>`.
## Tests
Junit4
Mockito Kotlin
AssertJ
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:$version'
Nomes = `returns complete name when person exists`
LiveData e Coroutines são assync. A seguinte rule torna as chamadas sincronizadas no teste, para isso uma rule foi criada, para tornar todas as interaçōes assíncronas, sincronas.
```
@get:Rule
val instantTaskRule = InstantTaskRule()
```
### View Model
Recebe e dispara mensagem. Expoe resultado por meio de streamming
#### Incomming Message
``` kotlin
@Mock
private lateinit var cartResumeObserver: Observer<CartResume>
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
cart = ShoppingCart().apply {
cartResume.observeForever(cartResumeObserver)
}
}
@Test
internal fun `set view to write mode when place id is minor than zero`() {
val viewStateFunction = mock<Observer<PlaceDetailsViewModel.ViewState>>()
viewModel.fetchPlace("")
verify(cartResumeObserver).onChanged(PlaceDetailsViewModel.ViewState.WRITE_STATE)
}
```
#### Outgoing Message
```kotlin
@Test
internal fun `tells place repository to insert the given place`() {
val place = placeDetailsData()
viewModel.savePlace(place)
verify(placeRepository).insert(
check { placeEntity ->
assertThat(placeEntity.name).isEqualTo("PlaceEntity Test")
assertThat(placeEntity.category).isEqualTo("Category Test")
assertThat(placeEntity.description).isEqualTo("Some Description")
}
)
}
```
#### Tips
Quando precisar de objetos complexos varias vezes criar fixtures utilizando padrão builder
``` kotlin
object PlaceEntityFixture {
fun createPlace(
val withId: String = UUID.fromRandom().toString()
val withName: String = UUID.fromRandom().toString()
val withLatitude: Double = Random.nextDouble()
): Place {
return Place(
id = withId,
name = withName,
latitude = withLatitude
)
}
fun createPlaces(numOfPlaces: Int): List<Place> {
return MutableList<Place>().apply {
repeat(numOfPlaces) { createPlace() }
}
}
}
```
quando estiver utilizando muitas variacoes do mesmo objeto data class
``` Kotlin
someDataObject().copy(name = "Other Name")
```
## Commits
### Mensagens
Tente responder basicamente duas perguntas ai fazer um commit.
> O que esse commit resolve?
> O que esse commit faz?
---
**Exemplo:**
`[IUE-000] Changes the welcome message on the splash screen`
O Commit acima resolve a issue **IUE-000** e muda o texto da mensagem de bem vindo da splash screen.