0% acharam este documento útil (0 voto)
36 visualizações34 páginas

Chapter 7 - Building Data Sources - Clean Android Architecture

Este capítulo aborda a construção de fontes de dados remotas e locais para uma arquitetura limpa no Android. Discute como implementar fontes de dados que usam Retrofit para buscar dados da internet e Room e Data Store para armazenamento local, separando essas fontes em módulos de biblioteca.

Enviado por

Marcus Passos
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
36 visualizações34 páginas

Chapter 7 - Building Data Sources - Clean Android Architecture

Este capítulo aborda a construção de fontes de dados remotas e locais para uma arquitetura limpa no Android. Discute como implementar fontes de dados que usam Retrofit para buscar dados da internet e Room e Data Store para armazenamento local, separando essas fontes em módulos de biblioteca.

Enviado por

Marcus Passos
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 34

26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

Capítulo 7 : Construindo Fontes de


Dados

Neste capítulo, continuaremos focando na camada de dados discutindo como


podemos implementar fontes de dados locais e remotas e as funções que elas
desempenham na arquitetura limpa. Primeiro, veremos como as fontes de da-
dos remotas podem ser construídas e como elas podem buscar dados da Inter-
net por meio de chamadas para Retrofit. Em seguida, veremos a implementa-
ção de fontes de dados locais e como elas podem interagir com o Room e o
Data Store para manter os dados localmente. Nos exercícios do capítulo, conti-
nuaremos os exercícios anteriores e adicionaremos as fontes de dados discuti-
das no capítulo, vendo como podemos conectá-las ao Room e Retrofit.

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

Construindo e usando fontes de dados remotas


Construindo e integrando fontes de dados locais

Ao final do capítulo, você terá aprendido o papel das fontes de dados, como
implementar fontes de dados remotas e locais que usam Retrofit, Room e Data
Store para gerenciar os dados de um aplicativo e como podemos separar essas
fontes de dados em módulos da biblioteca.

Requerimentos técnicos

Os requisitos de hardware e software são os seguintes:

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/Chapter7 .

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

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 1/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

Construindo e usando fontes de


dados remotas

Nissoseção, nósveremos como podemos construir fontes de dados remotas e


como podemos usá-las em combinação com Retrofit para buscar e manipular
dados da Internet.

Nos capítulos anteriores, definimos abstrações para fontes de dados das quais
os repositórios dependem para manipular dados. Isso ocorreu porque quería-
mos evitar que os repositórios tivessem dependências das fontes de dados e,
em vez disso, que as fontes de dados dependessem dos repositórios. Para fon-
tes de dados remotas, isso se parece com a figura a seguir:

Figura 7.1 – Um diagrama de classes de fonte de dados remota

A implementaçãoda fonte de dados remota tem duas funções. Istoinvocará a


camada de rede para buscar e manipular dados e converterá os dados para a

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 2/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

entidade de domínio ou, se necessário, para dados intermediários exigidos


pelo repositório.

Vejamos a entidade definida nos capítulos anteriores:

classe de dados Usuário(


    val id: String,
    val firstName: String,
    val lastName: String,
    val email: String
) {
    fun getFullName() = "$firstName $lastName"
}
Aqui, temos a mesma classe de dados User que foi definida como parte do do-
mínio. Agora vamos supornósestão buscando os seguintes dados da Internet
no formato JSON:

classe de dados UserApiModel(


    @Json(name = "id") val id: String,
    @Json(name = "first_name") val firstName: String,
    @Json(name = "last_name") val lastName: String,
    @Json(name = "email") val email: String
)
Aqui, temos uma classe UserApiModel na qual definimos os mesmos campos da
classe User e os anotamos com a anotação @Json , que faz parte da biblioteca
Moshi.

A abstração da fonte de dados remota se parece com o seguinte:

interface UserRemoteDataSource {
    fun getUser(id: String): Flow<User>
}
Esta é a abstração que definimos no capítulo anterior. Antes de escrevermos a
implementação desta classe, primeiro precisaremos especificar nosso serviço
de Retrofit:

interface UserService {
    @GET("/users/{userId}")
https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 3/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    suspend fun getUser(@Path("userId") userId: String):


        UserApiModel
}
Esta é uma classe de serviço Retrofit típica, que buscará uma classe UserApiMo‐
del do endpoint /users/{userId} . Agora podemos criar a implementação da

fonte de dados parabuscaro usuário de UserService :

classe de dados UserRemoteDataSourceImpl(private val


userService: UserService) : UserRemoteDataSource {
    override fun getUser(id: String): Flow<User> {
        fluxo de retorno {
            emit(userService.getUser(id))
        }.map {
            User(it.id, it.firstName, it.lastName,
                it.e-mail)
        }
    }
}
Aqui, implementamos a interface UserRemoteDataSource e, no método getUser
, invocamos o método getUser da dependência UserService . Uma vez que
UserApiModel é obtido, nós o convertemos para a classe User .

Nesta seção, vimos como podemos construir uma fonte de dados remota com a
ajuda da biblioteca Retrofit para manipular dados da Internet. Na seção a se-
guir, veremos um exercício que mostra como podemos implementar uma
fonte de dados remota.

Exercício 07.01 – Construindo uma fonte de


dados remota

Modifique o Exercício 06.01 – Criando repositórios para que um novo módulo


de biblioteca seja criado emEstúdio Android. Nomeie o módulo data-remote .
Este módulo dependerá do domínio e do repositório de dados . O módulo será
responsável por buscar usuários e posts como JSON de
https://jsonplaceholder.typicode.com/ .

O usuário terá a seguinte representação JSON:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 4/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

{
    "id": 1,
    "nome": "Leanne Graham",
    "username": "Bret",
    "email": "[email protected]"
}
A postagem terá a seguinte representação JSON:

{
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident
        occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae
conequuntur expedita et cum\nreprehenderit molestiae ut
ut quas totam\nnostrum rerum est autem sunt rem eveniet
architecto"
}
O módulo precisará implementar o seguinte:

UserApiModel e PostApiModel , que conterão os dados do JSON.

UserService , que retornará uma lista de UserApiModel da URL /users e Use‐

rApiModel com base no ID da URL /users/{userId} .

PostService , que retornará uma lista de PostApiModel do URL /posts e Pos‐

tApiModel com base no ID do URL /post/{postId} .

RemoteUserDataSourceImpl , que implementará RemoteUserDataSource , cha-

mará UserService e retornará Flow , que emite uma lista de objetos User ou
UseCaseException.UserException se houver um erro na chamada para User‐

Service . A mesma abordagem será adotada para retornar Usuário com

base no ID.
RemotePostDataSourceImpl que implementará RemotePostDataSource , cha-

mará PostService e retornará Flow , que emite uma listade objetos Post ou
UseCaseException.PostException se houver um erro na chamada para Post‐

Service . A mesma abordagem será adotada para retornar uma postagem

com base no ID.

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

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 5/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

1. Crie o módulo remoto de dados .


2. Crie as classes UserApiModel e UserService .
3. Crie as classes PostApiModel e PostService .
4. Crie as implementações de fontes de dados remotas para RemoteUserData‐
Source e RemotePostDataSource .

Siga estas etapas para concluir o exercício:

1. Crie um novo módulo chamado data-remote , que será um módulo de bibli-


oteca do Android.
2. Certifique-se de que no arquivo build.gradle de nível superior , as seguin-
tes dependências estejam definidas:
script de construção {
     …
    dependências {
        classpath gradlePlugins.android
        classpath gradlePlugins.kotlin
        classpath gradlePlugins.hilt
    }
}
3. No mesmo arquivo, adicioneas bibliotecas de rede para os mapeamentos
de biblioteca:
    extensão {
        …
        versões = [
                …
                okHttp: "4.9.0",
                adaptação: "2.9.0",
                moshi : "1.13.0",
                …
        ]
        …
        rede = [
                okHttp: "com.squareup.okhttp3:
                    okhttp:${versions.okHttp}",
                retrofit : "com.squareup.retrofit2
                    :retrofit:${versions.retrofit}",

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 6/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

                retrofitMoshi: "com.squareup.retrofit2
                    :conversor-moshi:$
                        {versions.retrofit}",
                moshi : "com.squareup.moshi:
                    moshi:${versions.moshi}",
                moshiKotlin : "com.squareup.moshi:
                    moshi-kotlin:${versions.moshi}"
        ]
        …
    }
4. Dentroo arquivo build.gradle do módulo data-remote , certifique-se de que
os seguintes plugins estejam presentes:
plug-ins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
5. No mesmo arquivo, altere as configurações para as definidas no arquivo
build.gradle de nível superior :
andróide {
    compileSdk defaultCompileSdkVersion
    configuração padrão {
        minSdk defaultMinSdkVersion
        targetSdk defaultTargetSdkVersion
        …
    }
    opções de compilação {
        sourceCompileVersion javaCompileVersion
        targetCompatibility javaCompileVersion
    }
    kotlinOptions {
        jvmTarget = jvmTarget
    }
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 7/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

Aqui, estamos nos certificando de que o novo módulo usará as mesmas confi-
gurações em relação àcompilação e a versão mínima e máxima do Android
como o restante do projeto, facilitando a alteração da configuração em todos
os módulos.

6. No mesmo arquivo, adicione as dependências às bibliotecas de rede e aos


módulos de repositório de dados e domínio :
dependências {
    implementação(projeto(caminho: ":domínio"))
    implementação(projeto(caminho: ":repositório de
dados"))
    implementação coroutines.coroutinesAndroid
    implementação network.okHttp
    rede de implementação.retrofit
    implementação network.retrofitMoshi
    implementação rede.moshi
    implementação network.moshiKotlin
    implementação di.hiltAndroid
    kapt di.hiltCompiler
    testImplementação test.junit
    testImplementação test.coroutines
    testImplementação test.mockito
}
7. No gradle.properties de nível superior , adicione a seguinte configuração
para moshi :
android.jetifier.ignorelist=moshi-1.13.0
8. No arquivo AndroidManifest.xml no módulo data-remote , adicione a inter-
netpermissão:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/andr
oid"
    package="com.clean.data_remote">
    <uses-permission
android:name="android.permission.INTERNET" />
</manifest>
9. No módulo data-remote , crie um novo pacote chamado networking .

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 8/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

10. No pacote de rede , crie um novo pacote chamado user .


11. No pacote do usuário , crie uma nova classe chamada UserApiModel :
classe de dados UserApiModel(
    @Json(name = "id") val id: Longo,
    @Json(name = "name") val name: String,
    @Json(name = "username") val username: String,
    @Json(name = "email") val email: String
)
12. No mesmo pacote, crie uma nova interface chamada UserService :
interface UserService {
    @GET("/usuários")
    suspend fun getUsers(): List<UserApiModel>
    @GET("/users/{userId}")
    suspend fun getUser(@Path("userId") userId: Long):
        UserApiModel
}
13. Dentroo pacote de rede , crie um novo pacote chamado post .
14. No pacote post , crie uma nova classe chamada PostApiModel :
classe de dados PostApiModel(
    @Json(name = "id") val id: Longo,
    @Json(name = "userId") val userId: Longo,
    @Json(name = "title") val title: String,
    @Json(name = "body") val body: String
)
15. No mesmo pacote, crie uma nova interface chamada PostService :
interface PostService {
    @GET("/post")
    suspend fun getPosts(): List<PostApiModel>
    @GET("/posts/{postId}")
    suspend fun getPost(@Path("postId") id: Longo):
        PostApiModel
}
16. No módulo data-remote , crie um novo pacote chamado source .
17. Dentroo pacote de origem , crie uma nova classe chamada RemoteUserDa‐
taSourceImpl :
class RemoteUserDataSourceImpl @Inject
constructor(private val userService: UserService):

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 9/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    RemoteUserDataSource {
    override fun getUsers(): Flow<List<User>> = flow {
        emit(userService.getUsers())
    }.map { usuários ->
        users.map { userApiModel ->
            convert(userApiModel)
        }
    }.truque {
        jogue UseCaseException.UserException(it)
    }
    override fun getUser(id: Long): Flow<User> = flow
{
        emit(userService.getUser(id))
    }.map {
        converta (ele)
    }.truque {
        jogue UseCaseException.UserException(it)
    }
    
    diversão privada convert(userApiModel:
UserApiModel) =
        User(userApiModel.id, userApiModel.name,
            userApiModel.username, userApiModel.email)
}

Aqui, invocamos os métodos getUsers e getUser de UserService e depois con-


vertemos os objetos UserApiModel em objetos User para evitar as outras cama-
dasdependendo dos dados relacionados à rede. O mesmo princípio se aplica
ao tratamento de erros. Se houver um erro de rede, como um código HTTP 404 ,
a exceção será HttpException , que faz parte da biblioteca Retrofit.

18. No pacote de origem , crie uma nova classe chamada RemotePostDataSour‐


ceImpl :
class RemotePostDataSourceImpl @Inject
constructor(private val postService: PostService):
    RemotePostDataSource {
    

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 10/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    override fun getPosts(): Flow<List<Post>> = flow {


        emit(postService.getPosts())
    }.map { postagens ->
        posts.map { postApiModel ->
            convert(postApiModel)
        }
    }.truque {
        throw UseCaseException.PostException(it)
    }
    override fun getPost(id: Long): Flow<Post> = flow
{
        emit(postService.getPost(id))
    }.map {
        converta (ele)
    }.truque {
        throw UseCaseException.PostException(it)
    }
    diversão privada convert(postApiModel:
PostApiModel) =
        Post(postApiModel.id, postApiModel.userId,
            postApiModel.title, postApiModel.body)
}

Aqui nóssiga o mesmo princípio da classe RemoteUserDataSourceImpl .

19. No módulo data-remote , crie um novo pacote chamado injeção :


20. No pacote de injeção , crie uma nova classe chamada NetworkModule :
@Módulo
@InstallIn(SingletonComponent::class)
class Módulo de Rede {
    @Provides
    fun fornecerOkHttpClient(): OkHttpClient =
        OkHttpClientName
        .Construtor()
        .readTimeout(15, TimeUnit.SECONDS)
        .connectTimeout(15, TimeUnit.SECONDS)
        .construir()

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 11/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    @Provides
    fun provideMoshi(): Moshi = Moshi.Builder().add
        (KotlinJsonAdapterFactory()).build()
    @Provides
    fun fornecerRetrofit(okHttpClient: OkHttpClient,
        moshi: Moshi): Retrofit = Retrofit.Builder()
        .baseUrl
             ("https://jsonplaceholder.typicode.com/")
        .client(okHttpClient)
        .addConverterFactory
            (MoshiConverterFactory.create(moshi))
        .construir()
    @Provides
    fun fornecerUserService(retrofit: Retrofit):
        Serviço de usuário =
        retrofit.create(UserService::class.java)
    @Provides
    fun providePostService(retrofit: Retrofit):
        PostServiço =
        retrofit.create(PostService::class.java)
}

Aqui, fornecemos as dependências Retrofit e OkHttp necessárias para a rede.

21. Dentroo pacote de injeção , crie uma classe chamada RemoteDataSourceMo‐


dule :
@Módulo
@InstallIn(SingletonComponent::class)
classe abstrata RemoteDataSourceModule {
    @Binds
    diversão abstrata
bindPostDataSource(postDataSourceImpl:
RemotePostDataSourceImpl): RemotePostDataSource
    @Binds
    diversão abstrata bindUserDataSource
        (userDataSourceImpl:
            RemoteUserDataSourceImpl):

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 12/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

RemoteUserDataSource
}

Aqui, usamos Hilt para vincular as implementações deste módulo com as abs-
trações definidas no módulo de repositório de dados .

22. Para testar a unidade do código, agora precisamos criar uma nova pasta
chamada resources na pasta de teste do módulo data-remote .
23. Dentro da pasta de recursos , crie uma pasta chamada mockito-extensions ;
dentro desta pasta, crie um arquivo chamado
org.mockito.plugins.MockMaker ; e dentro deste arquivo, adicione o se-

guinte texto – mock-maker-inline .


24. Crie uma classe de teste chamada RemoteUserDataSourceImplTest , que tes-
tará ocenários de sucesso para os métodos dentro de RemoteUserDataSour‐
ceImpl :
class RemoteUserDataSourceImplTest {
    valor privado userService = mock<UserService>()
    valor privado userDataSource =
        RemoteUserDataSourceImpl(userService)
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetUsers() = runBlockingTest {
        val remoteUsers = listOf(UserApiModel(1,
            "nome", "nome de usuário", "e-mail"))
        val usuários esperados = listOf(Usuário(1,
"nome",
            "nome de usuário", "e-mail"))
        sempre(userService.getUsers()).
            thenReturn(utilizadores remotos)
        val resultado =
userDataSource.getUsers().first()
        Assert.assertEquals(expectedUsers, result)
    }
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetUser() = runBlockingTest {
        val id = 1L

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 13/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

        val remoteUser = UserApiModel(id, "nome",


            "nome de usuário", "e-mail")
        val user = User(id, "nome", "nome de usuário",
            "o email")
        sempre(userService.getUser(id))
            .thenReturn(usuário remoto)
        val resultado = userDataSource.getUser(id).
            primeiro()
        Assert.assertEquals(usuário, resultado)
    }
}

Aqui estamoszombando da interface UserService e fornecendo dados de usuá-


rio simulados, que serão obtidos e convertidos por RemoteDataSourceImpl .

25. Na mesma classe de teste, adicione os cenários de erro:


class RemoteUserDataSourceImplTest {
    …
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetUsersThrowsError() = runBlockingTest {
        sempre(userService.getUsers()).thenThrow
            (Exceção de tempo de execução())
        userDataSource.getUsers().catch {
            Assert.assertTrue(é UseCaseException.
                Exceção de usuário)
        }.collect()
    }
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetUserThrowsError() = runBlockingTest {
        val id = 1L
        sempre(userService.getUser(id)).thenThrow
            (Exceção de tempo de execução())
        userDataSource.getUser(id).catch {
            Assert.assertTrue(é UseCaseException.
                Exceção de usuário)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 14/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

        }.collect()
    }
}

Aqui, estamos zombando de um erro gerado por UserService , que será con-
vertido por RemoteUserDataSourceImpl em UseCaseException.UserException .

26. Crie uma classe de teste chamada RemotePostDataSourceImplTest , que


terámétodos de teste como RemoteUserDataSourceImplTest para postagens:
class RemotePostDataSourceImplTest {
    valor privado postService = mock<PostService>()
    valor privado postDataSource =
        RemotePostDataSourceImpl(postService)
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetPosts() = runBlockingTest {
        val remotePosts = listOf(PostApiModel(1, 1,
            "título", "corpo"))
        val esperadoPosts = listOf(Post(1, 1,
"título",
            "corpo"))
        sempre(postService.getPosts()).thenReturn
            (posts remotos)
        val resultado =
postDataSource.getPosts().first()
        Assert.assertEquals(expectedPosts, result)
    }
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetPost() = runBlockingTest {
        val id = 1L
        val remotePost = PostApiModel(id, 1, "título",
            "corpo")
        val esperadoPost = Post(id, 1, "título",
            "corpo")
        sempre(postService.getPost(id)).thenReturn
            (RemotePost)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 15/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

        val resultado = postDataSource.getPost(id).


            primeiro()
        Assert.assertEquals(expectedPost, resultado)
    }
}

Aqui, estamos fazendo para postagens o que fizemos para usuários em Remo‐
teUserDataSourceImplTest .

27. Adicione ocenários de erro em RemotePostDataSourceImplTest :


class RemotePostDataSourceImplTest {
    …
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetPostsThrowsError() = runBlockingTest {
        sempre(postService.getPosts()).thenThrow
            (Exceção de tempo de execução())
        postDataSource.getPosts().catch {
            Assert.assertTrue(é UseCaseException.
                PostException)
        }.collect()
    }
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetPostThrowsError() = runBlockingTest {
        val id = 1L
        sempre(postService.getPost(id)).thenThrow
            (Exceção de tempo de execução())
        postDataSource.getPost(id).catch {
            Assert.assertTrue(é UseCaseException.
                PostException)
        }.collect()
    }
}

Se executarmos otestes, devemos ver algo como a figura a seguir:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 16/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

Figura 7.2 – Saída dos testes unitários da fonte de dados remota

Neste exercício, adicionamos um novo módulo ao aplicativo, no qual podemos


ver como podemos adicionar uma fonte de dados remota ao aplicativo. Para
buscar os dados, estamos usando bibliotecas como OkHttp e Retrofit e combi-
nando-as com a implementação da fonte de dadospara buscar usuários e pos-
tagens. Na seção a seguir, expandiremos o aplicativo para introduzir fontes de
dados locais, nas quais persistiremos os dados que estamos buscando aqui.

Construindo e integrando fontes de


dados locais

Nesta seção, nósvaianalisar como podemos construir fontes de dados locais e


integrá-las com bibliotecas como Room e Data Store.

As fontes de dados locais têm uma estrutura semelhante às fontes de dados re-
motas. As abstrações são fornecidas pelas camadas acima, e as implementa-
ções são responsáveis ​por invocar métodos de frameworks de persistência e
converter dados em entidades, como a figura a seguir:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 17/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

Figura 7.3 – Um diagrama de fonte de dados local

Vamos assumirnóster a mesma UserEntity definida nos capítulos anteriores:

classe de dados Usuário(


    val id: String,
    val firstName: String,
    val lastName: String,
    val email: String
) {
    fun getFullName() = "$firstName $lastName"
}
Vamos fazeramesma suposição sobre UserLocalDataSource :

interface UserLocalDataSource {
    suspender a diversão insertUser(user: User)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 18/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    fun getUser(id: String): Flow<User>


}
Agora precisamos fornecer uma implementação para essa fonte de dados que
manipulará os dados do Room. Primeiro, precisamos definir uma entidade de
usuário para Room:

@Entity(tableName = "usuário")
classe de dados UserEntity(
    @PrimaryKey @ColumnInfo(name = "id") val id: String,
    @ColumnInfo(name = "first_name") val firstName:
String,
    @ColumnInfo(name = "last_name") val lastName: String,
    @ColumnInfo(name = "email") val email: String
)
Agora, podemos definir UserDao , que consulta um usuário por um ID e insere
um usuário:

@Dao
interface UserDao {
    @Query("SELECT * FROM usuário onde id = :id")
    fun getUser(id: String): Flow<UserEntity>
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    divertido insertUser(users: UserEntity)
}
finalmente, oimplementação da fonte de dados pareceisto:

class UserLocalDataSourceImpl(private val userDao:


UserDao) : UserLocalDataSource {
    override suspend fun insertUser(user: User) {
        userDao.insertUser(UserEntity(user.id,
            user.firstName, user.lastName, user.email))
    }
    override fun getUser(id: String): Flow<User> {
        return userDao.getUser(id).map {
            User(it.id, it.firstName, it.lastName,
                it.e-mail)
        }

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 19/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    }
}
Aqui, a fonte de dados local invoca UserDao para inserir e recuperar um usuá-
rio e converte a entidade de domínio em uma entidade Room.

Se quisermos usar o Data Store em vez do Room com uma implementação de


armazenamento de dados local, podemostenhoalgo como o exemplo a seguir:

valor privado KEY_ID = stringPreferencesKey("key_id")


valor privado KEY_FIRST_NAME =
    stringPreferencesKey("key_first_name")
valor privado KEY_LAST_NAME =
    stringPreferencesKey("key_last_name")
valor privado KEY_EMAIL =
stringPreferencesKey("key_email")
class UserLocalDataSourceImpl(private val dataStore:
    DataStore<Preferences>) : UserLocalDataSource {
    override suspend fun insertUser(user: User) {
        dataStore.edit {
            it[KEY_ID] = user.id
            it[KEY_FIRST_NAME] = user.firstName
            it[KEY_LAST_NAME] = user.lastName
            it[KEY_EMAIL] = usuário.email
        }
    }
    override fun getUser(id: String): Flow<User> {
        return dataStore.data.map {
            Do utilizador(
                it[KEY_ID].ouEmpty(),
                it[KEY_FIRST_NAME].ouEmpty(),
                it[KEY_LAST_NAME].ouEmpty(),
                it[KEY_EMAIL].ouEmpty()
            )
        }
    }
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 20/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

Aqui, usamos uma chave para cada um dos campos do objeto User para arma‐
zenar os dados. O método getUser não usa o ID para procurar um usuário, o

que mostra que, para esse caso de uso específico, Room é o método mais
apropriado.

Nesta seção, vimos como podemos construir uma fonte de dados local com a
ajuda do Room eDadosArmazene bibliotecas para poder consultar e persistir
dados localmente em um dispositivo. A seguir, veremos um exercício para
mostrar como podemos implementar um armazenamento de dados local.

Exercício 07.02 – Construindo uma fonte de


dados local

Modifique o Exercício 07.01 – Construindo uma fonte de dados remota para que
um novo módulo de biblioteca Androiddata-local nomeado é criado. Este mó-
dulo dependerá do domínio e do repositório de dados .

O módulo implementará o seguinte:

UserEntity e PostEntity , que manterá os dados a serem persistidos de User

e Post usando Room


UserDao e PostDao , que serão responsáveis ​por persistir e buscar uma lista

de UserEntity e PostEntity
LocalUserDataSourceImpl e LocalPostDataSourceImpl , que serão responsá-

veis ​por invocar os objetos UserDao e PostDao para persistir os dados e para
converter os dados em objetos User e Post
LocalInteractionDataSourceImpl , que será responsável por persistir o ob-

jeto Interaction

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

1. Crie o módulo local de dados .


2. Crie as classes UserEntity e PostEntity .
3. Crie os DAOs para usuários e postagens.
4. Crie as implementações da fonte de dados.

Siga estas etapas para concluir o exercício:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 21/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

1. Crioum novo módulo chamado data-local , que será um módulo de biblio-


teca do Android.
2. Certifique-se de que no arquivo build.gradle de nível superior , as seguin-
tes dependências estejam definidas:
script de construção {
     …
    dependências {
        classpath gradlePlugins.android
        classpath gradlePlugins.kotlin
        classpath gradlePlugins.hilt
    }
}
3. No mesmo arquivo, adicione as bibliotecas de persistência aos mapeamen-
tos de biblioteca:
    extensão {
        …
        versões = [
                …
                sala: "2.4.0",
                armazenamento de dados: "1.0.0",
                …
        ]
        …
        persistência = [
                roomRuntime : "androidx.room:room-
                    tempo de
execução:${versions.room}",
                roomKtx : "androidx.room:room-
                     ktx:${versions.room}",
                roomCompiler: "androidx.room:room-
                    compilador:${versions.room}",
                armazenamento de dados:
"androidx.datastore:
                    preferências do armazenamento de
dados:$
                       {versions.datastore}"
        ]

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 22/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

        …
    }
4. Dentroo arquivo build.gradle do módulo data-local , certifique-se de que
os seguintes plugins estejam presentes:
plug-ins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
5. No mesmo arquivo, altereas configurações para as definidas no arquivo
build.gradle de nível superior :
andróide {
    compileSdk defaultCompileSdkVersion
    configuração padrão {
        minSdk defaultMinSdkVersion
        targetSdk defaultTargetSdkVersion
        …
    }
    opções de compilação {
        sourceCompileVersion javaCompileVersion
        targetCompatibility javaCompileVersion
    }
    kotlinOptions {
        jvmTarget = jvmTarget
    }
}
6. No mesmo arquivo, adicione as dependências às bibliotecas de rede e aos
módulos de repositório de dados e domínio :
dependências {
    implementação(projeto(caminho: ":domínio"))
    implementação(projeto(caminho: ":repositório de
dados"))
    implementação coroutines.coroutinesAndroid
    persistência de implementação.roomRuntime
    persistência de implementação.roomKtx
    kapt persistence.roomCompiler

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 23/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    implementação persistence.datastore
    implementação di.hiltAndroid
    kapt di.hiltCompiler
    testImplementação test.junit
    testImplementação test.coroutines
    testImplementação test.mockito
}
7. No módulo local de dados , crie um novo pacote chamado db .
8. No pacote db , crieum novo pacote chamado user .
9. No pacote do usuário , crie a classe UserEntity :
@Entity(tableName = "usuário")
classe de dados UserEntity(
    @PrimaryKey @ColumnInfo(name = "id") val id:
Longo,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "username") val nome de
usuário:
        Corda,
    @ColumnInfo(name = "email") val email: String
)
10. No mesmo pacote, crie a interface UserDao :
@Dao
interface UserDao {
    @Query("SELECT * FROM usuário")
    fun getUsers(): Flow<List<UserEntity>>
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    divertido insertUsers(users: List<UserEntity>)
}
11. No pacote db , crie um novo pacote chamado post .
12. No pacote de postagem , crieuma nova classe chamada PostEntity :
@Entity(tableName = "post")
classe de dados PostEntity(
    @PrimaryKey @ColumnInfo(name = "id") val id:
Longo,
    @ColumnInfo(name = "userId") val userId: Longo,
    @ColumnInfo(name = "title") val title: String,
    @ColumnInfo(name = "body") val body: String

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 24/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

)
13. No mesmo pacote, crie uma nova interface chamada PostDao :
@Dao
interface PostDao {
    @Query("SELECT * FROM post")
    fun getPosts(): Flow<List<PostEntity>>
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    divertido insertPosts(users: List<PostEntity>)
}
14. No pacote db , crie a classe AppDatabase :
@Database(entities = [UserEntity::class,
PostEntity::class], versão = 1)
classe abstrata AppDatabase : RoomDatabase() {
    diversão abstrata userDao(): UserDao
    diversão abstrata postDao(): PostDao
}
15. Dentroo módulo data-local , crie um novo pacote chamado source .
16. No pacote de origem , crie uma nova classe chamada LocalUserDataSour‐
ceImpl :
class LocalUserDataSourceImpl @Inject
constructor(private val userDao: UserDao):
    LocalUserDataSource {
    substituir fun getUsers(): Flow<List<User>> =
        userDao.getUsers().map { users ->
        usuários.mapa {
            User(it.id, it.name, it.username,
                it.e-mail)
        }
    }
    override suspend fun addUsers(users: List<User>) =
        userDao.insertUsers(users.map {
        UserEntity(it.id, it.name, it.username,
            it.e-mail)
    })
}

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 25/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

Aqui, no método getUsers , recuperamos uma lista de objetos UserEntity do


UserDao e os convertemos em objetos User . No método addUsers , fazemos o

contrário, pegando uma lista de objetos User a serem inseridos e convertendo-


os em objetos UserEntity .

17. No mesmopacote, crie a classe LocalPostDataSourceImpl :


class LocalPostDataSourceImpl @Inject
constructor(private val postDao: PostDao):
    LocalPostDataSource {
    substituir fun getPosts(): Flow<List<Post>> =
       postDao.getPosts().map { posts ->
        posts.map {
            Post(it.id, it.userId, it.title, it.body)
        }
    }
    override suspend fun addPosts(posts: List<Post>) =
        postDao.insertPosts(posts.map {
        PostEntity(it.id, it.userId, it.title,
            it.body)
    })
}

Aqui, seguimos a mesma abordagem que usamos para LocalUserDataSour‐


ceImpl .

18. No mesmopacote, crie a classe LocalInteractionDataSourceImpl :


valor interno KEY_TOTAL_TAPS =
intPreferencesKey("key_total_taps")
class LocalInteractionDataSourceImpl @Inject
constructor(private val dataStore:
DataStore<Preferences>):
    LocalInteractionDataSource {
    override fun getInteraction(): Flow<Interaction> {
        return dataStore.data.map {
            Interação(it[KEY_TOTAL_TAPS] ?: 0)
        }
    }

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 26/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    substituir suspend fun


saveInteraction(interaction:
        Interação) {
        dataStore.edit {
            isso[KEY_TOTAL_TAPS] =
                interação.totalCliques
        }
    }
}

Aqui, usamos a biblioteca Preference Data Store para persistir o objeto Inte-
raction, mantendo chaves diferentes para cada campo da classe Interaction e,
neste caso, será apenas uma chave para o total de cliques.

19. No módulo local de dados , crie um novo pacote chamado injeção .


20. Dentroo pacote de injeção , crie uma nova classe chamada PersistenceMo‐
dule :
val Context.dataStore: DataStore<Preferences> por
preferencesDataStore(name = "my_preferences")
@Módulo
@InstallIn(SingletonComponent::class)
class PersistênciaModule {
    @Provides
    divertido fornecerAppDatabase(@ApplicationContext
        contexto: Contexto): AppDatabase =
        Room.databaseBuilder(
            contexto,
            AppDatabase::class.java, "meu-banco de
dados"
        ).construir()
    @Provides
    fun provideUserDao(appDatabase: AppDatabase):
        UserDao = appDatabase.userDao()
    @Provides
    fun providePostDao(appDatabase: AppDatabase):
        PostDao = appDatabase.postDao()
    @Provides

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 27/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    diversão fornecerLocalInteractionDataSourceImpl
        (@ApplicationContext contexto: Contexto) =
        LocalInteractionDataSourceImpl(context.dataSto
re)
}

Aqui, fornecemos todas as dependências de Data Store e Room.

21. No mesmopacote, crie uma nova classe chamada LocalDataSourceModule ,


na qual conectamos as abstrações às ligações:
@Módulo
@InstallIn(SingletonComponent::class)
classe abstrata LocalDataSourceModule {
    @Binds
    diversão abstrata bindPostDataSource
       (lostDataSourceImpl: LocalPostDataSourceImpl):
           LocalPostDataSource
    @Binds
    diversão abstrata bindUserDataSource
        (userDataSourceImpl: LocalUserDataSourceImpl):
            LocalUserDataSource
    @Binds
    diversão abstrata bindInteractionDataStore
        (interactionDataStore:LocalInteractionData
            SourceImpl): LocalInteractionDataSource
}
22. Para testar a unidade do código, agora precisaremos criar uma nova pasta
chamada resources na pasta de teste do módulo local de dados .
23. Dentro da pasta de recursos , crie uma pasta chamada mockito-extensions ;
lado de dentronesta pasta, crie um arquivo chamado
org.mockito.plugins.MockMaker ; e dentro deste arquivo, adicione o se-

guinte texto – mock-maker-inline .


24. Crie a classe de teste LocalUserDataSourceImplTest :
class LocalUserDataSourceImplTest {
    private val userDao = mock<UserDao>()
    valor privado userDataSource =
        LocalUserDataSourceImpl(userDao)

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 28/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    @ExperimentalCoroutinesApi
    @Teste
    fun testGetUsers() = runBlockingTest {
        val localUsers = listOf(UserEntity(1, "nome",
            "nome de usuário", "e-mail"))
        val usuários esperados = listOf(Usuário(1,
"nome",
            "nome de usuário", "e-mail"))
        sempre(userDao.getUsers()).thenReturn
            (flowOf(localUsers))
        val resultado =
userDataSource.getUsers().first()
        Assert.assertEquals(expectedUsers, result)
    }
    @ExperimentalCoroutinesApi
    @Teste
    fun testAddUsers() = runBlockingTest {
        val localUsers = listOf(UserEntity(1, "nome",
            "nome de usuário", "e-mail"))
        val usuários = listOf(Usuário(1, "nome", "nome
de usuário",
            "o email"))
        userDataSource.addUsers(usuários)
        verifique(userDao).insertUsers(localUsers)
    }
}

Aqui estamoszombando da classe UserDao e usando-a para fornecer dados si-


mulados para LocalUserDataSourceImpl , que converterá os dados de e para os
objetos User .

25. Crie a classe de teste LocalPostDataSourceImplTest :


class LocalPostDataSourceImplTest {
    private val postDao = mock<PostDao>()
    valor privado postDataSource =
        LocalPostDataSourceImpl(postDao)
    @ExperimentalCoroutinesApi

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 29/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

    @Teste
    fun testGetPosts() = runBlockingTest {
        val localPosts = listOf(PostEntity(1, 1,
            "título", "corpo"))
        val esperadoPosts = listOf(Post(1, 1,
"título",
            "corpo"))
        sempre(postDao.getPosts()).thenReturn
            (flowOf(localPosts))
        val resultado =
postDataSource.getPosts().first()
        Assert.assertEquals(expectedPosts, result)
    }
    @ExperimentalCoroutinesApi
    @Teste
    fun testAddUsers() = runBlockingTest {
        val localPosts = listOf(PostEntity(1, 1,
            "título", "corpo"))
        val posts = listOf(Post(1, 1, "título",
            "corpo"))
        postDataSource.addPosts(posts)
        verifique(postDao).insertPosts(localPosts)
    }
}

Aqui, realizamos o mesmo tipo de testes para postagens que fizemos em Loca‐
lUserDataSourceImplTest para usuários.

26. Crioa classe de teste LocalInteractionDataSourceImplTest :


class LocalInteractionDataSourceImplTest {
    valor privado dataStore = mock<DataStore
        <Preferências>>()
    valor privado interaçãoDataSource =
        LocalInteractionDataSourceImpl(dataStore)
    @ExperimentalCoroutinesApi
    @Teste
    fun testGetInteraction() = runBlockingTest {

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 30/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

        val cliques = 10
        val interação = Interação(cliques)
        val preferências = mock<Preferences>()
        sempre(preferências[KEY_TOTAL_TAPS]).
            entãoReturn(cliques)
        sempre(dataStore.data).thenReturn
            (flowOf(preferências))
        val resultado = interaçãoDataSource.
            getInteraction().first()
        assertEquals(interação, resultado)
    }
    @ExperimentalCoroutinesApi
    @Teste
    fun testSaveInteraction() = runBlockingTest {
        val cliques = 10
        val interação = Interação(cliques)
        val preferências = mock<MutablePreferences>()
        sempre(preferences.toMutablePreferences())
            .thenReturn(preferências)
        sempre(dataStore.updateData(any())).
            A resposta {
            runBlocking {
                it.getArgument<suspender
(Preferências) -
                 > Preferências>
(0).invoke(preferências)
            }
            preferências
        }
        interaçãoDataSource.saveInteraction(interação)
        verificar(preferências)[KEY_TOTAL_TAPS] =
cliques
    }
}

Aqui, no método testSaveInteraction , precisamos simular o método update‐


Data em vez do método de edição da classe DataStore . Isso ocorre porque o

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 31/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

método de edição é uma função de extensão que não pode ser simulada com
as bibliotecas atuais que temos e, em vez disso, deve contar com o método que
ele invoca, que é updateData .

Se executarmos os testes,deve ver algo como a figura a seguir:

Figura 7.4 – Saída dos testes unitários da fonte de dados local

Se desenharmos um diagrama dos módulos no exercício, veremos algo como a


figura a seguir:

https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 32/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

Figura 7.5 – Diagrama do módulo do exercício 07.02

Nós podemos verque os módulos :data-remote e :data-local estão isolados um


do outro. Os dois módulos têm responsabilidades diferentes e lidam com de-
pendências diferentes. :data-remote lida com a busca de dados da Internet, en-
quanto :data-local lida com a persistência de dados localmente no SQLite
usando Room e arquivos usando Data Store. Isso dá mais flexibilidade ao
nosso código porque podemos alterar a forma como buscamos os dados – por
exemplo, sem afetar a forma como persistimos os dados.

Neste exercício, criamos um novo módulo no aplicativo no qual lidamos com


fontes de dados locais. Para persistir os dados, usamos bibliotecas como Room
e Data Store, e temosintegrou-os com o armazenamento de dados local.

Resumo

Neste capítulo, examinamos o conceito de fontes de dados e os diferentes tipos


de fontes de dados que temos disponíveis em um aplicativo Android. Começa-
mos com fontes de dados remotas e vimos alguns exemplos de como podemos
https://learning.oreilly.com/library/view/clean-android-architecture/9781803234588/B18320_07_ePub.xhtml 33/34
26/10/2022 21:44 Chapter 7: Building Data Sources | Clean Android Architecture

construir uma fonte de dados e combiná-la com bibliotecas como Retrofit e


OkHttp. A fonte de dados local seguiu princípios semelhantes ao remoto e,
aqui, usamos bibliotecas como Room e Data Store para implementar isso.

Nos exercícios, implementamos as fontes de dados como parte de diferentes


módulos. Isso foi feito para evitar a criação de dependências desnecessárias
entre as outras camadas do aplicativo e as estruturas específicas que usamos
para as fontes de dados. No próximo capítulo, veremos como podemos cons-
truir a camada de apresentação e mostrar os dados ao usuário. Também ex-
ploraremos como podemos dividir a camada de apresentação em módulos se-
parados e navegar de uma tela em um módulo para uma tela em outro mó-
dulo, através da introdução de módulos que podem ser compartilhados por
outros módulos de apresentaçã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_07_ePub.xhtml 34/34

Você também pode gostar