# 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. ![enter image description here](https://i.ibb.co/31q68Vp/data-architecture-flow.png =350x) :::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.