You are on page 1of 34

26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

Capítulo 5 : Construindo o Domínio


de um Aplicativo Android

Neste capítulo, analisaremos como é a arquitetura de um aplicativo Android e


suas três camadas principais ( apresentação , domínio e dados ). Em seguida,
aprenderemos como podemos traduzi-lo em uma arquitetura limpa e focar na
camada de domínio, que fica no centro da arquitetura. A seguir, veremos o pa-
pel que ele desempenha na arquitetura de um aplicativo e quais são suas enti-
dades e casos de uso. Por fim, veremos um exercício, no qual veremos como
podemos configurar um projeto Android Studio com vários módulos e usá-los
para estruturar a camada de domínio.

Neste capítulo, abordaremos os seguintes tópicos:

Apresentando a arquitetura do aplicativo


Criando a camada de domínio

Ao final deste capítulo, você estará familiarizado com a camada de domínio de


um aplicativo, entidades de domínio e casos de uso.

Requerimentos técnicos

Estes são os requisitos de hardware e software:

Estúdio Android – Raposa do Ártico | 2020.3.1 Patch 3

Os arquivos de código para este capítulo podem ser encontrados aqui:


https://github.com/PacktPublishing/Clean-Android-
Architecture/tree/main/Chapter5 .

Confira o vídeo a seguir para ver o Código em Ação: https://bit.ly/3826FH6

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 1/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

Apresentando a arquitetura do
aplicativo

Nesta seção, nósdiscutirá a arquitetura mais comum que pode ser aplicada a
um aplicativo Android e como ela pode ser combinadacom princípios de ar-
quitetura limpa e veja como devemos estruturar idealmente nossa base de
código.

Nos exercícios dos capítulos anteriores, vimos como, para uma aplicação que
requer a integração de várias fontes de dados para rede e persistência, tive-
mos que colocar muita lógica dentro da classe ViewModel . Nesses exemplos, o
ViewModel tinha várias responsabilidades, incluindo buscar os dados da Inter-

net, persistir localmente e manter as informações necessárias na interface do


usuário. Além dessas responsabilidades extras, o ViewModel também tinha
muitas dependências nas diferentes fontes de dados; isso significa que uma al-
teração nas bibliotecas de rede ou persistência exigiria uma alteração no View‐
Model. Para resolver esse problema, nosso código precisaria ser dividido em

camadas separadas com responsabilidades diferentes. Normalmente, as cama-


das se parecem com a figura a seguir:

Figura 5.1 – Um diagrama de arquitetura de aplicativo

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 2/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

Na Figura 5.1 , nóspode ver que existem três camadas comresponsabilidades


diferentes:

Camada de apresentação : EstaA camada é responsável por exibir os da-


dos na tela (também conhecida como camada de interface do usuário ).
estegeralmente contém classes necessárias para gerenciar a interface do
usuário e classes que executarão a lógica relacionada à interface do usuá-
rio, como ViewModels .
Camada de domínio : esta camada éresponsável por buscar dados da ca-
mada de dados e executar a lógica de negócios que pode ser reutilizada em
um aplicativo.
Camada de dados : esta camadaé responsável por lidar com a lógica de ne-
gócios de uma aplicação que lida com o gerenciamento de dados.

Podemos aplicar os princípios de arquitetura limpa sobre a arquitetura em ca-


madas colocando a camada de domínio no centro, conforme mostrado na Fi-
gura 5.2 , e tornando-a o local para armazenar nossas entidades e casos de uso
. Nas camadas externas estão as camadas de apresentação e de dados, que
sãorepresentada pela camada adaptadora de interface (representada por
ViewModels e Repositories ) e pela camada de estrutura (representadapela in-

terface do usuário e estruturas de persistência e rede):

Figura 5.2 – Um diagrama de dependência da camada de aplicativo

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 3/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

Nofigura anterior, podemos ver que as dependências entre a camada de domí-


nio e a camada de dados estão invertidas. A camada de domínio ainda extrairá
dados da camada de dados, mas por ter dependências invertidas, será menos
impactada por quaisquer alterações nessa camada, assim como se ocorrerem
alterações na camada de apresentação, elas não afetarão a camada de domí-
nio. Se o aplicativo sofrer alterações nos casos de uso, ele conduzirá as altera-
ções na apresentação e na camada de dados.

Para separar as camadas,pode usar módulos Android . Isso nos ajudará a im-
por mais rigor ao projeto, evitando dependências indesejadas entre as cama-
das. Isso também ajuda a melhorar os tempos de compilação em aplicativos
grandes devido ao cache de compilação do Gradle, que apenas reconstruirá os
módulos que tiveram alterações no código. Isso será algo como a figura a
seguir:

Figura 5.3 – Um diagrama de módulo de aplicativo

Nós podemos ver issonão há um número limitado de módulos que precisamos


para cada camada ou que devemos ter módulos correspondentes entre as três
camadas. A expansão de cada camada pode ser impulsionada por diferentes
fatores, como as fontes de dados, os usos do aplicativo, as tecnologias e os pro-
tocolos usados ​nessas fontes de dados (usando APIs REST para determinados
dados e Bluetooth ou comunicação de campo próximo para outros dados ti-
pos). O uso dos casos de uso pode ser outro fator (como ter um determinado
conjunto de casos de uso para uso com vários aplicativos). Podemos querer ex-

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 4/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

pandir a camada de apresentação por causa de como certas telas são agrupa-
das para formar certos recursos e fluxos isolados dentro de um aplicativo
(como uma seção de configurações do aplicativo ou um fluxo de
login/inscrição). Um aspecto interessante a ser observado é o :appmódulo, que
tem a função de combinar todas as dependências e montá-las. Aqui, vamos
reunir todas as dependências necessárias e inicializá-las.

Uma coisa importante a notar aqui é que os módulos não são equivalentes às
próprias camadas; módulos de dados podem ter dependências para módulos
de dados de nível inferior. De fato, essa situação ocorrerá em cenários em que
um módulo de uma camada precisará ter uma dependência de outro módulo
da mesma camada. Se fôssemos criar uma dependênciaentre os dois, podemos
acabar com uma dependência cíclica, o que não é desejado. Nessa situação,
precisaremos criar um módulo comum entre os dois que conterá as dependên-
cias necessárias. Por exemplo, se quisermos navegar de uma tela em
:presentation1 para uma tela em :presentation2 ou qualquer uma das outras,

precisaremos criar um novo módulo do qual dependerão todos os módulos de


apresentação e que armazenará os dados ou lógica necessária para lidar com
a navegação. Veremos essa questão com mais detalhes quando discutirmos a
camada de apresentação.

Para criar um novo módulo do Android Studio, você precisa clicar com o bo-
tão direito do mouse no projeto no Android Studio, selecionar New e depois
Module , conforme mostrado na figura a seguir:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 5/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

Figura 5.4 – Criando um novo módulo Android Studio

Você será entãosolicitado a selecionar o tipo de módulo e, dependendo da fun-


cionalidade, você pode selecionar Android Library se o módulo não contiver
código da estrutura do Android ou Java ou Kotlin Library se o módulo não ti-
ver dependências da estrutura do Android. Nos exercícios a seguir, usaremos
bibliotecas Android. Uma vez criado o módulo, ele já conterá um conjunto de
arquivos e pastas gerados. Um dos mais relevantes será o arquivo
build.gradle . A seção do plug-in no arquivo indicará que uma biblioteca An-

droid foi criada:

plug-ins {
    id 'com.android.library'
    …
}
Se quisermos adicionar uma dependência ao módulo recém-criado, podemos
usar o seguinte no módulo app :

dependências {
https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 6/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    implementação(projeto(caminho: ":meu-novo-módulo"))
    …
}
A sintaxe para adicionar uma dependência a um módulo é semelhante à sin-
taxe para adicionar uma dependência externa e é através do método de imple‐
mentação Gradle . O resto indica que o módulo do aplicativo irádependem de

outro módulo dentro do mesmo projeto.

Nesta seção, analisamos as camadas da arquitetura de aplicativos Android e


como podemos aplicar princípios de arquitetura limpa a essas camadas. Na se-
ção a seguir, veremos como podemos construir uma camada de domínio.

Criando a camada de domínio

Nesta seção, vamosdiscuta como construir a camada de domínio e o que entra


nela por meio de alguns exemplos. Finalmente, veremos um exercício no qual
uma camada de domínio é criada.

Como a camada de domínio fica no centro do aplicativo, ela precisará ter um


número mínimo de dependências. Isso significa que os módulos Gradle que
formam a camada de domínio precisarão ser os módulos mais estáveis ​do pro-
jeto. Isso é para evitar que outros módulos sejam alterados devido a uma alte-
ração que ocorreu em uma dependência que os módulos de domínio usam. O
domínio deve ser responsável por definir as entidades e os casos de uso da
aplicação.

As entidades são representadas por objetos que contêm dados e são principal-
mente imutáveis. Vamos supor que queremos representar um usuário como
uma entidade. Podemos terminar com algo como o seguinte:

classe de dados Usuário(


    val id: String,
    val firstName: String,
    val lastName: String,
    val email: String
) {
    fun getFullName() = "$firstName $lastName"

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 7/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

}
Aqui, usamos uma classe de dados simples e declaramos todos os nossos cam-
pos imutáveis ​com a palavra-chave val . Também temos uma função de lógica
de negócios para este objeto, que retornará o nome completo do usuário.

Em seguida, precisamos definir nossos casos de uso. Como os casos de uso pre-
cisarão obter dados da camada de dados, primeiro precisaremos criar uma
abstração para nosso repositório e terminaremos coma seguir:

interface UserRepository {
    fun getUser(id: String): Usuário
}
Aqui, temos apenas um método simples que retornará um usuário baseado em
id . Agora podemos criar um caso de uso para recuperar o usuário:

class GetUserUseCase(private val userRepository:


UserRepository) {
    fun getUser(id: String) = userRepository.getUser(id)
}
No exemplo anterior, definimos um caso de uso para recuperar o usuário, que
terá uma dependência do UserRepository , que será usado para recuperar as
informações do usuário. Se observarmos o exemplo anterior, podemos ver um
pouco de redundância porque o caso de uso não tem lógica extra e apenas re-
torna o valor do repositório. O benefício dos casos de uso vem quando quere-
mos combinar vários resultados de vários repositórios.

Vamos supor que queremos associar o usuário a um local específico, definido


da seguinte forma:

classe de dados Localização(


    val id: String,
    val userId: String,
    val lat: Duplo,
    val long: Duplo
)
Aqui, nós apenasmanter a latitude e longitude associadas a um determinado
usuário. Agora, vamos supor que teríamos um repositório para os diferentes
locais:
https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 8/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

interface LocalizaçãoRepositório {
    fun getLocation(userId: String): Localização
}
Aqui, novamente temos uma abstração de um repositório com um método
para obter um local específico baseado em userId . Se quisermos obter um
usuário e um local associado, precisaremos criar um caso de uso específico
para isso:

class GetUserWithLocationUseCase(
    valor privado userRepository: UserRepository,
    valor privado locationRepository: LocationRepository
) {
    divertido getUser(id: String) =
        UserWithLocation(userRepository.getUser(id),
locationRepository.getLocation(id))
}
classe de dados UserWithLocation(
    val usuário: usuário,
    localização do valor: Localização
)
No exemplo anterior, criamos uma nova entidade chamada UserWithLocation ,
que armazenará User e Location . UserWithLocation será usado como resultado
para o método getUser em GetUserWithLocationUseCase . Isso dependerá de
UserRepository e LocationRepository para buscar os dados relevantes.

Podemos melhorar ainda mais os casos de uso manipulando o encadeamento


também. Porque os casos de usolidam principalmente com recuperação e ge-
renciamento de dados, que precisam ser assíncronos, devemos lidar com isso
em um thread separado. Podemos usar fluxos Kotlin paragerenciar isso, e po-
demos acabar com algo assim para os repositórios:

interface UserRepository {
    fun getUser(id: String): Flow<User>
}
interface LocalizaçãoRepositório {
    fun getLocation(id: String): Flow<Location>
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 9/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

Aqui, alteramos os tipos de retorno dos métodos para um fluxo Kotlin, que
pode emitir um fluxo de dados ou um único item. Agora, podemos combinar
os diferentes fluxos no fluxo no caso de uso:

class GetUserWithLocationUseCase(
    valor privado userRepository: UserRepository,
    valor privado locationRepository: LocationRepository
) {
    fun getUser(id: String) = combine(
        userRepository.getUser(id),
        locationRepository.getLocation(id)
    ) { usuário, local ->
        UserWithLocation(usuário, localização)
    }.flowOn(Dispatchers.IO)
}
Aqui nóscombine os fluxos User e Location em um fluxo UserWithLocation e
executaremos a busca de dados no dispatcher de IO .

Muitas vezes, ao lidar com carregamento e gerenciamento de dados, especial-


mente da Internet, podemos encontrar diferentes erros, que teremos que levar
em consideração em nossos casos de uso. Para resolver isso, podemos definir
entidades de erro. Existem muitas possibilidades para defini-los, incluindo es-
tender a classe Throwable , definir uma determinada classe de dados, uma
combinação das duas ou combiná-las com classes seladas:

classe selada UseCaseException(override val cause:


Throwable?) : Throwable(causa) {
    class UserException(causa: Throwable):
        UseCaseException(causa)
    
    class LocationException(causa: Throwable):
        UseCaseException(causa)
    
    class UnknownException(causa: Throwable):
        UseCaseException(causa)
    objeto companheiro {
        fun extractException(lançável: Throwable):

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 10/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

            UseCaseException {
            return if (lançável é UseCaseException)
                throwable else UnknownException(lançável)
        }
    }
}
Aqui, temoscriou uma classe selada que terá como subclasses um erro dedi-
cado para cada entidade, além de um erro desconhecido que lidará com erros
que não consideramos e um método complementar que verificará um objeto
Throwable e retornará UnknownException para qualquer Throwable que seja 't

UseCaseException . Precisaremos garantir que o erro seja propagado pelo fluxo

de fluxo, mas primeiro podemos combinar a entidade para sucesso com a enti-
dade para erro para garantir que o consumidor do caso de uso não precise ve-
rificar o tipo de Throwable novamente e fazer um elenco. Podemos fazer isso
com a seguinte abordagem:

classe selada Resultado<out T : Qualquer> {


    classe de dados Success<out T : Any>(val data: T):
        Resultado<T>()
    class Error(val exception: UseCaseException):
        Resultado<Nada>()
}
Aqui, definimos uma classe Result selada, que terá duas subclasses para su-
cesso e erro. A classe Success conterá os dados relevantes para o caso de
uso e a classe Error conterá as exceções definidas anteriormente. A classe

Error pode ser expandida ainda mais, se necessário, para manter os dados,

bem como o erro, se quisermos exibir os dados armazenados em cache ou per-


sistidos como um espaço reservado. Agora podemos modificar o caso de uso
para incorporar a classe Resulte o estado de erro:

class GetUserWithLocationUseCase(
    valor privado userRepository: UserRepository,
    valor privado locationRepository: LocationRepository
) {
    fun getUser(id: String) = combine(
        userRepository.getUser(id),
        locationRepository.getLocation(id)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 11/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    ) { usuário, local ->


        Result.Success(UserWithLocation(user, location))
como
            Resultado<UserWithLocation>
    }.flowOn(Dispatchers.IO)
        .truque {
            emit(Result.Error(UseCaseException.
                extractException(it)))
        }
}
Aqui, retornamos Result.Success , que manterá o objeto UserWithLocation se
nenhum erro ocorrer, e adicionamos use o operador catch para emitir
Result.Error com UseCaseException que ocorreu durante a busca dos dados.

Como essas operações se repetirão para vários casos de uso, podemos usar a
abstração para criar um modelo de como cada caso de uso se comporta e dei-
xar as implementações lidarem apenas com o processamento do
necessário.dados. Um exemplo pode ter a seguinte aparência:

classe abstrata UseCase<T : Any, R : Any>(private val


dispatcher: CoroutineDispatcher) {
    fun execute(input: T): Flow<Result<R>> =
        executeData(entrada)
        .map {
            Result.Success(it) as Result<R>
        }
        .flowOn(despachante)
        .truque {
            emit(Result.Error(UseCaseException.
                extractException(it)))
        }
    diversão abstrata interna executeData(input: T):
Flow<R>
}
No exemplo anterior, definimos uma classe abstrata que conterá o método
execute , que invocará o método executeData abstrato e, em seguida, mape-

ará o resultado desse método em um objeto Result , seguido pela configuração


do fluxo em um CoroutineDispatcher e, finalmente, manipulando os erros no

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 12/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

operador catch . A implementação deste será semelhante ao seguinte. Ob-


serve que a palavra-chave interna para o método executeData só tornará o mé-
todo acessível no módulo atual. Isso ocorre porque queremos apenas que o
método execute seja chamado pelos usuários deste caso de uso:

class GetUserWithLocationUseCase(
    despachante: CoroutineDispatcher,
    valor privado userRepository: UserRepository,
    valor privado locationRepository: LocationRepository
): UseCase<String, UserWithLocation>(dispatcher) {
    substituir fun executeData(input: String):
        Fluxo<UserWithLocation> {
        retornar combinar(
            userRepository.getUser(entrada),
            locationRepository.getLocation(input)
        ) { usuário, local ->
            UserWithLocation(usuário, localização)
        }
    }
}
NissoPor exemplo, GetUserWithLocationUseCase só terá que lidar com o retorno
dos dados necessários relevantes para o caso de uso no método executeData .
Podemos usar genéricos para vincular os tipos de dados que queremos que o
caso de uso processe, introduzindo outras abstrações para a entrada e saída
necessárias:

classe abstrata UseCase<T : UseCase.Request, R :


UseCase.Response>(private val dispatcher:
CoroutineDispatcher) {
    …
    Solicitação de interface
    Resposta da interface
}
Aqui, vinculamos os genéricos na classe UseCase a duas interfaces – Request e
Response . O primeiro é representado pelos dados de entrada requeridos pelo

caso de uso, e o último érepresentado pela saída do caso de uso. A implemen-


tação agora ficará assim:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 13/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

class GetUserWithLocationUseCase(
    despachante: CoroutineDispatcher,
    valor privado userRepository: UserRepository,
    valor privado locationRepository: LocationRepository
    ): UseCase< GetUserWithLocationUseCase.Request,
    GetUserWithLocationUseCase.Response >(despachante) {
    substituir fun executeData(input: Request ): Flow
        < Resposta > {
        retornar combinar(
            userRepository.getUser(input.userId),
            locationRepository.getLocation(input.userId)
        ) { usuário, local ->
            Resposta (UserWithLocation(usuário, local))
        }
    }
    classe de dados Request(val userId: String): UseCase.
        Solicitar
    classe de dados Response(val userWithLocation:
        UserWithLocation): UseCase.Response
}
Aqui, fornecemos as implementações para as classes Request e Response e as
usamos ao estender a partir da classe base. Nesse caso, as classes Request e
Responserepresentar objetos de transporte de dados . Quando criamos tem-

plates para casos de uso, é importante observar sua evolução, pois conforme a
complexidade aumenta, o template pode se tornar inadequado.

Muitas vezes, teremos a oportunidade de construir um novo caso de uso a par-


tir de casos de uso menores existentes. Vamossuponha que, para recuperar
um usuário e um local, temos dois casos de uso separados:

class GetUserUseCase(
    despachante: CoroutineDispatcher,
    valor privado userRepository: UserRepository
    ): UseCase<GetUserUseCase.Request,
    GetUserUseCase.Response>(despachante) {
    substituir fun executeData(input: Request): Flow
        <Resposta> {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 14/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

        return userRepository.getUser(input.userId)
            .map {
                Resposta(isso)
            }
    }
    classe de dados Request(val userId: String): UseCase.
        Solicitar
    data class Response(val user: User): UseCase.Response
}
class GetLocationUseCase(
    despachante: CoroutineDispatcher,
    valor privado locationRepository: LocationRepository
    ): UseCase<GetLocationUseCase.Request,
    GetLocationUseCase.Response>(expedidor) {
    substituir fun executeData(input: Request): Flow
        <Resposta> {
        return
locationRepository.getLocation(input.userId)
            .map {
                Resposta(isso)
            }
    }
    classe de dados Request(val userId: String): UseCase
        .Solicitar
    data class Response(val location: Location) :
UseCase.
        Resposta
}
Nos exemplos anteriores, temos duas classes para cada caso de uso para recu-
perar um usuário e um local.

Podemosmodifique GetUserWithLocationUseCase para usar os casos de uso


existentes, como este:

class GetUserWithLocationUseCase(
    despachante: CoroutineDispatcher,
    valor privado getUserUseCase: GetUserUseCase ,

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 15/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    valor privado getLocationUseCase: GetLocationUseCase


    ): UseCase<GetUserWithLocationUseCase.Request,
        GetUserWithLocationUseCase.Response>(expedidor) {
    substituir fun executeData(input: Request): Flow
        <Resposta> {
        return combinar( getUserUseCase.executeData
                (GetUserUseCase.Request(input.userId)) ,
            getLocationUseCase.executeData
                (GetLocationUseCase.Request(input.userId)
)
        ) { userResponse , locationResponse ->
            Response(UserWithLocation( userResponse.user
,
                localizaçãoResposta.local ))
        }
    }
    classe de dados Request(val userId: String): UseCase
        .Solicitar
    classe de dados Response(val userWithLocation:  
         UserWithLocation): UseCase.Response
}
Aqui, alteramos as dependências para usar dois casos de uso existentes em vez
dos repositórios, invocamos o método executeData de cada um e, em seguida,
criamos uma nova resposta usando as respostas de ambos os casos de uso.

Nissoseção, vimos como construir uma camada de domínio com entidades, ca-
sos de uso e abstrações para repositórios. Na seção a seguir, veremos um exer-
cício relacionado à construção de uma camada de domínio.

Exercício 05.01 – Construindo uma camada de


domínio

Neste exercício, vamoscrie um novo projeto no Android Studio no qual


umserá criado um novo módulo Gradle chamado domain . Este módulo con-
terá entidades contendo os seguintes dados:

Usuário : terá um ID do tipo Longo e um nome, nome de usuário e e-mail.

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 16/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

Post : Isso terá um ID e um ID de usuário como um tipo Longo , um título e

um corpo.
Interação : Isso conterá o número total de interações com o aplicativo.

Erros : Isso é para quando postagens ou usuários não podem ser

carregados.

Os seguintes casos de uso precisarão ser definidos para o aplicativo:

Recuperando uma lista contendo postagens com informações do usuário,


agrupadas com os dados de interação
Recuperando informações sobre um usuário específico com base no ID
Recuperando informações sobre uma postagem específica com base no ID
Atualizando os dados de interação

Para concluir este exercício, você precisará fazer o seguinte:

Crie um novo projeto no Android Studio.


Crie um mapeamento de todas as dependências da biblioteca e as versões
no arquivo root build.gradle .
Crie o módulo de domínio no Android Studio.
Crie as entidades necessárias para os dados e erros.
Crie a classe Result , que conterá o cenário de sucesso e erro.
Crie as abstrações do repositório para obter as informações de usuário,
postagem e interação.
Crie os quatro casos de uso necessários.

Siga estespassos para completar o exercício:

1. Crie um novo projeto no Android Studio e selecione Empty Compose


Activity .
2. No arquivo root build.gradle , adicione as seguintes configurações que se-
rão usadas para todos os módulos do projeto:
script de construção {
    extensão {
        javaCompileVersion = JavaVersion.VERSION_1_8
        jvmTarget = "1.8"
        defaultCompileSdkVersion = 31
        defaultTargetSdkVersion = 31
https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 17/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

        defaultMinSdkVersion = 21
        …
}
3. No mesmo arquivo, adicione as versões das bibliotecas que serão utilizadas
pelos módulos Gradle:
script de construção {
    extensão {
        …
        versões = [
                androidGradlePlugin: "7.0.4",
                kotlin: "1.5.31",
                punho: "2.40.5",
                coreKtx : "1.7.0",
                appCompat: "1.4.1",
                compor: "1.0.5",
                lifecycleRuntimeKtx: "2.4.0",
                activityCompose: "1.4.0",
                material: "1.5.0",
                corrotinas: "1.5.2",
                junit : "4.13.2",
                mockito : "4.0.0",
                expressoJunit : "1.1.3",
                espressoCore : "3.4.0"
        ]
        …
}
4. No mesmoarquivo, adicione um mapeamento para as dependências do plu-
gin que todo o projeto usará:
script de construção {
    extensão {
        …
        gradlePlugins = [
                android: "com.android.tools.build:
                    gradle:${versions.
                        androidGradlePlugin}",
                kotlin:
"org.jetbrains.kotlin:kotlin-    

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 18/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

                    gradle-plugin:${versions.kotlin}",
                hilt : "com.google.dagger:hilt-
                    plug-in android-gradle:
                        ${versions.hilt}"
        ]
        …
}
5. A seguir, você vaiprecisa adicionar as dependências às bibliotecas androidx
:
script de construção {
    extensão {
        …
        androidx = [
                core : "androidx.core:core-
ktx:${versions.coreKtx}",
                appCompat :
"androidx.appcompat:appcompat:${versions.appCompat}",
                composeUi :
"androidx.compose.ui:ui:${versions.compose}",
                composeMaterial :
"androidx.compose.material:material:${versions.compose
}",
                composeUiToolingPreview:
"androidx.compose.ui:ui-tooling-
preview:${versions.compose}",
                lifecycleRuntimeKtx :
"androidx.lifecycle:lifecycle-runtime-
ktx:${versions.lifecycleRuntimeKtx}",
                composeActivity :
"androidx.activity:activity-
compose:${versions.activityCompose}"
        ]
        …
}
6. A seguir, adicione obibliotecas restantes para design de material, injeção
de dependência e testes:
script de construção {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 19/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    extensão {
        …
        matéria = [
                material: "com.google.android.
                    material: material: $
                        {versions.material}"
        ]
        corrotinas = [
                coroutinesAndroid: "org.jetbrains.
                    kotlinx:kotlinx-coroutines-
                       android:${versions.coroutines}"
        ]
        di = [
                hiltAndroid : "com.google.dagger:hilt-
                    android:${versions.hilt}",
                hiltCompiler: "com.google.dagger:hilt-
                    compilador:${versions.hilt}"
        ]
        teste = [
                junho:
                    "junit:junit:${versions.junit}",
                corrotinas: "org.jetbrains.kotlinx:
                    kotlinx-coroutines-test:
                        ${versions.coroutines}",
                mockito : "org.mockito.kotlin:
                   mockito-kotlin:${versions.mockito}"
        ]
        teste android = [
                junit: "androidx.test.ext
                    :junit:${versions.espressoJunit}",
                espressoCore : "androidx.test.
                    espresso:espresso-core:$
                        {versions.espressoCore}",
                composeUiTestJunit: "androidx.compose.
                ui:ui-test-junit4:${versions.compose}"
        ]
    }

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 20/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    …
}
7. No mesmo arquivo, você precisará substituir os mapeamentos anteriores
como dependências de plugins:
script de construção {
     …
    dependências {
        classpath gradlePlugins.android
        classpath gradlePlugins.kotlin
        classpath gradlePlugins.hilt
    }
}
8. Agora, você precisa alternar para o arquivo build.gradle no módulo do
aplicativo e alterar oconfigurações existentes com as definidas no
build.gradle de nível superior :
andróide {
    compileSdk defaultCompileSdkVersion
    configuração padrão {
        …
        minSdk defaultMinSdkVersion
        targetSdk defaultTargetSdkVersion
        versãoCódigo 1
        versionName "1.0"
        …
    }
    …
    opções de compilação {
        sourceCompileVersion javaCompileVersion
        targetCompatibility javaCompileVersion
    }
    kotlinOptions {
        jvmTarget = jvmTarget
        useIR = verdadeiro
    }
    construirRecursos {
        compor verdadeiro
    }

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 21/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    composeOptions {
        kotlinCompilerExtensionVersion
            versões.compor
    }
    …
}
9. No mesmo, você precisará substituir as dependências pelas definidas no ar-
quivo build.gradle de nível superior :
dependências {
    implementação androidx.core
    implementação androidx.appCompat
    material de implementação.material
    implementação androidx.composeUi
    implementação androidx.composeMaterial
    implementação androidx.composeUiToolingPreview
    implementação androidx.lifecycleRuntimeKtx
    implementação androidx.composeActivity
    testImplementação test.junit
}
10. No Android Studio, execute o comando Sync Project with Gradle Files e,
em seguida, o comando Make Project para garantir que o projeto seja com-
pilado sem erros.
11. Crie umnovo módulo para o projeto chamado domain , que será um módulo
de biblioteca Android.
12. No arquivo build.gradle do módulo de domínio , certifique-se de ter os se-
guintes plugins:
plug-ins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
13. No mesmo arquivo, certifique-se de usar as configurações definidas no ar-
quivo build.gradle de nível superior :
andróide {
    compileSdk defaultCompileSdkVersion
    configuração padrão {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 22/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

        minSdk defaultMinSdkVersion
        targetSdk defaultTargetSdkVersion
        …
    }
    …
    opções de compilação {
        sourceCompileVersion javaCompileVersion
        targetCompatibility javaCompileVersion
    }
    kotlinOptions {
        jvmTarget = jvmTarget
    }
}
14. No mesmoarquivo, você precisará adicionar as seguintes dependências:
dependências {
    implementação coroutines.coroutinesAndroid
    implementação di.hiltAndroid
    kapt di.hiltCompiler
    testImplementação test.junit
    testImplementação test.coroutines
    testImplementação test.mockito
}
15. Sincronize o projeto com os arquivos Gradle e compile o projeto nova-
mente para garantir que a configuração do Gradle esteja correta.
16. No módulo de domínio , crie um novo pacote chamado entity .
17. No pacote de entidade , crieuma classe chamada Post , que terá id , use‐
rId , title e body :
classe de dados Post(
    val id: Longo,
    val userId: Longo,
    val título: String,
    val corpo: String
)
18. No mesmo pacote, crie a classe User , que terá id , name , username e email :
classe de dados Usuário(
    val id: Longo,
    nome do valor: String,

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 23/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    val nome de usuário: String,


    val email: String
)
19. Em seguida, crie uma classe chamada PostWithUser , que conterá a postagem
e as informações do usuário :
classe de dados PostWithUser(
    val post: Post,
    val usuário: usuário
)
20. No mesmo pacote, crie uma classe chamada Interaction , que conterá o nú-
mero total de cliques:
classe de dados Interação(val totalClicks: Int)
21. Agora nósprecisa criar as entidades de erro:
classe selada UseCaseException(causa: Throwable) :
Throwable(causa) {
    class PostException(causa: Throwable):
        UseCaseException(causa)
    class UserException(causa: Throwable):
        UseCaseException(causa)
    class UnknownException(causa: Throwable):
        UseCaseException(causa)
    objeto companheiro {
        fun createFromThrowable(throwable: Throwable):
            UseCaseException {
            return if (lançável é UseCaseException)
            throwable else UnknownException(lançável)
        }
    }
}

Aqui, temos exceções definidas para quando haverá um problema ao carregar


o post e as informações do usuário, e UnknownException , que será emitida
quando algo der errado.

22. A seguir, vamoscrie a classe Result , que conterá as informações de sucesso


e erro:
classe selada Resultado<out T : Qualquer> {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 24/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    classe de dados Success<out T : Any>(val data: T):


        Resultado<T>()
    class Error(val exception: UseCaseException):
        Resultado<Nada>()
}
23. Agora, precisamos passar a definir as abstrações para os repositórios e,
para isso, criamos um novo pacote chamado repository .
24. No pacote do repositório , crie uma interface para gerenciar os dados da
postagem:
interface PostRepository {
    fun getPosts(): Flow<List<Post>>
    fun getPost(id: Long): Flow<Post>
}
25. No mesmo pacote, crie uma interface para gerenciar os dados do usuário:
interface UserRepository {
    fun getUsers(): Flow<List<User>>
    fun getUser(id: Long): Flow<User>
}
26. No mesmo pacote, crie uma interface para gerenciar os dados de interação:
interface InteractionRepository {
    fun getInteraction(): Flow<Interaction>
    divertido salvarInteração(interação: Interação):
        Fluxo<Interação>
}
27. Agora nóspasse para os casos de uso e comece criando um novo pacote cha-
mado usecase .
28. Neste pacote, crie o modelo UseCase :
classe abstrata UseCase<I : UseCase.Request, O :
UseCase.Response>(private val configuration:
Configuration) {
    fun execute(solicitação: I) =
processo(solicitação)
        .map {
            Result.Success(it) as Result<O>
        }
        .flowOn(configuração.dispatcher)
        .truque {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 25/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

            emit(Result.Error(UseCaseException.
                createFromThrowable(it)))
        }
    processo abstrato interno divertido (solicitação:
I): Flow<O>
    class Configuration(val dispatcher:
        CoroutineDispatcher)
    Solicitação de interface
    Resposta da interface
}

Nissotemplate, definimos a abstração dos objetos de transferência de dados,


bem como uma classe Configuration que contém CoroutineDispatcher . A ra-
zão para esta classe Configuration é poder adicionar outros parâmetros para o
caso de uso sem modificar as subclasses UseCase . Temos um método abstrato ,
que será implementado pelas subclasses para recuperar os dados dos repositó-
rios, e o método execute , que pegará os dados e os converterá em Result , ma-
nipulará os cenários de erro e definirá o CoroutineDispatcher .

29. No pacote de casos de uso, crie o caso de uso para recuperar a lista de pos-
tagens com as informações do usuário e os dados de interação:
class GetPostsWithUsersWithInteractionUseCase @Inject
construtor(
    configuração: configuração,
    valor privado postRepository: PostRepository,
    valor privado userRepository: UserRepository,
    private val transactionRepository:
        InteractionRepository
    ): GetPostsWithUsersWithInteractionUseCase
    GetPostsWithUsersWithInteractionUseCase {
    substituir o processo divertido (solicitação:
Solicitação):
        Fluxo<Resposta> =
        combinar(
            postRepository.getPosts(),
            userRepository.getUsers(),
            interaçãoRepository.getInteraction()

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 26/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

        ) { postagens, usuários, interação ->


            val postUsers = posts.map { post ->
                val usuário = usuários.primeiro {
                    it.id == post.userId
                }
                PostWithUser(post, usuário)
            }
            Resposta(postUsuários, interação)
        }
    Solicitação de objeto: UseCase.Request
    classe de dados Resposta(
        val posts: List<PostWithUser>,
        val interação: interação
    ): UseCase.Response
}

Nesta classe, estendemos a classe UseCase e, no método process , combinamos


as postagens, usuários e fluxos de interação. Como não há necessidade de en-
trada, a classe Request terá que estar vazia e a classe Response conterá uma
lista de informações combinadas de usuário e postagem, bem como os dados
de interação. A anotação @Inject nos ajudará a injetar esse caso de uso poste-
riormente na camada de apresentação.

30. Nomesmo pacote, crie o caso de uso para recuperar um post por ID:
class GetPostUseCase @Inject construtor(
    configuração: configuração,
    valor privado postRepository: PostRepository
    ): UseCase<GetPostUseCase.Request,
    GetPostUseCase.Response>(configuração) {
    substituir processo divertido (solicitação:
Solicitação): Fluxo
        <Resposta> =
        postRepository.getPost(request.postId)
            .map {
                Resposta(isso)
            }

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 27/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    classe de dados Request(val postId: Long):


UseCase.
        Solicitar
    data class Response(val post: Post): UseCase.
        Resposta
}
31. Nomesmo pacote, crie o caso de uso para recuperar um usuário por ID:
class GetUserUseCase @Inject construtor(
    configuração: configuração,
    valor privado userRepository: UserRepository
    ): UseCase<GetUserUseCase.Request,
    GetUserUseCase.Response>(configuração) {
    substituir processo divertido (solicitação:
Solicitação): Fluxo
        <Resposta> =
        userRepository.getUser(request.userId)
            .map {
                Resposta(isso)
            }
    classe de dados Request(val userId: Long):
UseCase.
        Solicitar
    data class Response(val user: User): UseCase.
        Resposta
}
32. Agora, passamos para o último caso de uso para atualizar os dados de
interação:
class UpdateInteractionUseCase @Inject construtor(
    configuração: configuração,
    private val transactionRepository:
        InteractionRepository
    ): UseCase<UpdateInteractionUseCase.Request,
    UpdateInteractionUseCase.Response>(configuração) {
    substituir processo divertido (solicitação:
Solicitação): Fluxo
        <Resposta> {
        retornar interaçãoRepository.saveInteraction

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 28/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

            (solicitação.interação)
            .map {
                Resposta
            }
    }
    classe de dados Request(val interação: Interação):
         UseCase.Request
    Resposta do objeto: UseCase.Response
}
33. Para teste de unidadeo código, precisamos criar uma nova pasta chamada
resources na pasta de teste do módulo de domínio .

34. Dentro da pasta de recursos , crie uma subpasta chamada mockito-extensi‐


ons ; dentro desta pasta, crie um arquivo chamado

org.mockito.plugins.MockMaker ; e dentro deste arquivo, adicione o se-

guinte texto – mock-maker-inline . Isso permite que a biblioteca de testes


Mockito zombe da classe Java final , que em Kotlin significa todas as clas-
ses sem a palavra-chave open .
35. Crie um novoclasse chamada UseCaseTest na pasta de teste do módulo de
domínio :
class UseCaseTest {
    @ExperimentalCoroutinesApi
    configuração de valor privado =
UseCase.Configuration
        (TestCoroutineDispatcher())
    solicitação de valor privado =
mock<UseCase.Request>()
    resposta de valor privado = mock<UseCase.Response>
()
    @ExperimentalCoroutinesApi
    private lateinit var useCase:
        UseCase<UseCase.Request, UseCase.Response>
    @ExperimentalCoroutinesApi
    @Antes da
    configuração divertida() {
        useCase = objeto: UseCase<UseCase.Request,
            UseCase.Response>(configuração) {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 29/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

            substituir o processo divertido


(solicitação: Solicitação):  
                Fluxo<Resposta> {
                assertEquals(this@UseCaseTest.request,
                    solicitar)
                return flowOf(resposta)
            }
        }
    }
}

Aqui, fornecemos uma implementação para a classe UseCase , que retornará


uma resposta simulada.

36. A seguir, crieum método de teste que verificará o cenário bem-sucedido


para o método execute :
    @ExperimentalCoroutinesApi
    @Teste
    fun testExecuteSuccess() = runBlockingTest {
        val resultado =
useCase.execute(request).first()
        assertEquals(Result.Success(resposta),
resultado)
    }

Aqui, afirmamos que o resultado do método execute é Success e que contém a


resposta simulada.

37. Em seguida, crie uma nova classe de teste chamada GetPostsWithU‐


sersWithInteractionUseCaseTest :
class GetPostsWithUsersWithInteractionUseCaseTest {
    private val postRepository = mock<PostRepository>
()
    valor privado userRepository =
mock<UserRepository>()
    private val transactionRepository =
mock<InteractionRepository>()

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 30/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

    valor privado useCase =


GetPostsWithUsersWithInteractionUseCase(
        zombar(),
        postRepositório,
        userRepository,
        interaçãoRepositório
    )
}

Aqui, simulamos todos os repositórios e injetamos os simulados na classe que


queremos testar.

38. Por fim, crieum método de teste que verificará o método de processo do
caso de uso que estamos testando:
    @ExperimentalCoroutinesApi
    @Teste
    fun testProcess() = runBlockingTest {
        val user1 = User(1L, "nome1", "username1",
"email1")
        val user2 = User(2L, "nome2", "username2",
"email2")
        val post1 = Post(1L, user1.id, "title1",
"body1")
        val post2 = Post(2L, user1.id, "title2",
"body2")
        val post3 = Post(3L, user2.id, "title3",
"body3")
        val post4 = Post(4L, user2.id, "title4",
"body4")
        val interação = Interação(10)
        sempre(userRepository.getUsers()).thenReturn
            (flowOf(listOf(usuário1, usuário2)))
        sempre(postRepository.getPosts()).thenReturn
          (flowOf(listOf(post1, post2, post3, post4)))
sempre(interactionRepository.getInteraction
            ()).thenReturn(flowOf(interação))
        val resposta = useCase.process

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 31/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

            (GetPostsWithUsersWithInteractionUseCase.
                Solicitar).first()
        assertEquals(
            GetPostsWithUsersWithInteractionUseCase.
                Resposta(
                lista de(
                    PostWithUser(post1, user1),
                    PostWithUser(post2, user1),
                    PostWithUser(post3, user2),
                    PostWithUser(post4, user2),
                ), interação
            ),
            resposta
        )
    }

Aqui, fornecemos listas simuladas de usuários e postagens e uma interação si-


mulada, depois as retornamos para cada uma das chamadas do repositório e
afirmamos que o resultado é uma lista de quatro postagens, escritas por dois
usuários e a interação simulada.

Se executarmos os testes para esses dois métodos, eles devem passar. Para tes-
tar os casos de uso restantes, podem ser aplicados os mesmos princípios que
usamos para GetPostsWithUsersWithInteractionUseCaseTest – criar repositó-
rios simulados, injetá-los no objeto que desejamos testar e, em seguida, definir
os simulados para a entrada do método do processo e os resultados que deve-
mos esperar, o que nos dará saída comomostrado na captura de tela a seguir:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 32/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

Figura 5.5 – Saída dos testes unitários do caso de uso

Nesta seção, realizamos um exercício no qual criamos um domínio simples


que continha entidades, alguns casos de uso simples e um caso de uso especí-
fico que combinava várias fontes de dados. O módulo de domínio tem depen-
dências de fluxos e Hilt. Isso significa que alterações nessas bibliotecas podem
causar alterações em nosso módulo de domínio. Essa decisão foi tomada de-
vido aos benefícios que essas bibliotecas oferecem quando se trata de progra-
mação reativa e injeção de dependência. Como consideramos a injeção de de-
pendência ao definir os casos de uso, isso os tornou mais testáveis, pois pode-
ríamos injetar objetos simuladosnos objetos testados com muita facilidade.

Resumo

Neste capítulo, vimos como a arquitetura de um aplicativo Android está em


camadas e focada na camada de domínio, discutindo os tópicos de entidades e
casos de uso. Também aprendemos como usar a inversão de dependência para
colocar casos de uso e entidades no centro de nossa arquitetura. Fizemos isso
criando abstrações de repositório que podem ser implementadas nas camadas
inferiores. Também aprendemos como usar módulos de biblioteca para impor
separações entre camadas.

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 33/34
26/10/2022 21:43 Chapter 5: Building the Domain of an Android Application | Clean Android Architecture

No exercício do capítulo, criamos um módulo de domínio para um aplicativo


Android, fornecendo um exemplo de como pode ser uma camada de domínio.
No próximo capítulo, focaremos na camada de dados, na qual forneceremos
implementações para as abstrações de repositório que definimos na camada
de domínio, e discutiremos como podemos usar esses repositórios para geren-
ciar os dados de uma aplicação.

Apoiar Sair

© 2022 O'REILLY MEDIA, INC.  TERMOS DE SERVIÇO POLÍTICA DE PRIVACIDADE

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_05_ePub.xhtml#_idParaDest-71 34/34

You might also like