Quase qualquer aplicativo Android que você criar precisará se conectar à Internet em algum momento. Neste
tutorial e nos seguintes, você constrói um aplicativo que se conecta a um serviço da web para recuperar e
exibir dados. Você também constrói o que aprendeu em tutoriais anteriores sobre ViewModel
, LiveData
e RecyclerView
.
Neste tutorial, você usa bibliotecas desenvolvidas pela comunidade para construir a camada de rede. Isso simplifica muito a busca de dados e imagens e também ajuda o aplicativo a estar em conformidade com algumas práticas recomendadas do Android, como carregar imagens em uma thread de segundo plano e armazenar em cache as imagens carregadas. Para as seções assíncronas ou sem bloqueio dentro do código, como falar com a camada de serviços da web, você modificará o aplicativo para usar corrotinas do Kotlin. Você também atualizará a interface do usuário do aplicativo se a Internet estiver lenta ou indisponível para que o usuário saiba o que está acontecendo.
safeArgs
para passar dados entre fragmentos.ViewModel
,
ViewModelProvider.Factory
, LiveData
e LiveData
.
Neste tutorial (e nos tutoriais a seguir), você trabalha com um aplicativo inicial chamado MarsRealEstate, que mostra propriedades à venda em Marte. Este aplicativo se conecta a um serviço da web para recuperar e exibir os dados da propriedade, incluindo detalhes como o preço e se a propriedade está disponível para venda ou aluguel. As imagens que representam cada propriedade são fotos da vida real de Marte, capturadas pelos robôs de Marte da NASA.
A versão do aplicativo que você constrói neste tutorial não terá muito brilho visual: Ela se concentra na parte da camada de rede do aplicativo para se conectar à Internet e baixar os dados de propriedade brutos usando um serviço da web. Para garantir que os dados sejam recuperados e analisados corretamente, você apenas imprimirá o número de propriedades em Marte em uma vista de texto:
A arquitetura do aplicativo MarsRealEstate tem dois módulos principais:
RecyclerView
.O aplicativo possui um ViewModel
para cada fragmento. Para este tutorial, você cria uma camada
para o serviço de rede e o ViewModel
se comunica diretamente com essa camada de rede. Isso é
semelhante ao que você fez em tutoriais anteriores, quando o ViewModel
se comunicou com o banco
de dados Room
.
O ViewModel
de visão geral é responsável por fazer a chamada de rede para obter as informações
imobiliárias da Mars. O ViewModel
de detalhe contém detalhes para a única parte do estado
real de Marte que é exibida no fragmento de detalhe. Para cada ViewModel
, você usa
LiveData
com vinculação de dados que reconhece o ciclo de vida para atualizar a IU do
aplicativo quando os dados mudam.
Você usa o componente Navigation para navegar entre os dois fragmentos e para passar a propriedade selecionada como um argumento.
Nesta tarefa, você baixa e executa o aplicativo inicial para MarsRealEstate e se familiariza com a estrutura do projeto.
app/java/MainActivity.kt
. O aplicativo usa fragmentos para ambas as telas, então a
única tarefa para a atividade é carregar o layout da atividade.app/res/layout/activity_main.xml
. O layout da atividade é o host para os dois
fragmentos, definidos no arquivo de navegação. Este layout instancia um NavHostFragment
e
seu controlador de navegação associado com o recurso nav_graph
.app/res/navigation/nav_graph.xml
. Aqui você pode ver a relação de navegação entre os
dois fragmentos. O grafo de navegação StartDestination
aponta para o
overviewFragment
, então o fragmento de visão geral é instanciado quando o aplicativo é
iniciado. detail
, network
e
overview
. Eles correspondem aos três componentes principais do seu aplicativo: A visão geral
e os fragmentos de detalhes e o código da camada de rede.app/java/overview/OverviewFragment.kt
. O OverviewFragment
inicializa
lentamente o OverviewViewModel
, o que significa que o OverviewViewModel
é
criado na primeira vez que é usado.onCreateView()
. Este método infla o layout fragment_overview
usando vinculação de dados, define o proprietário do ciclo de vida de vinculação para si mesmo
(this
) e define a variável viewModel
no binding
o objeto a ele.
Como definimos o proprietário do ciclo de vida, qualquer LiveData
usado na vinculação de
dados será automaticamente observado para quaisquer alterações e a IU será atualizada de acordo.app/java/overview/OverviewViewModel
. Como a resposta é um LiveData
e
definimos o ciclo de vida da variável de vinculação, qualquer alteração nele atualizará a IU do
aplicativo.init
. Quando o ViewModel
é criado, ele chama o método
getMarsRealEstateProperties()
.getMarsRealEstateProperties()
. Neste aplicativo inicial, este método
contém um espaço reservado para resposta. O objetivo deste tutorial é atualizar a resposta
LiveData
dentro do ViewModel
usando dados reais que você obtém da Internet.
app/res/layout/fragment_overview.xml
. Este é o layout do fragmento de visão geral com
o qual você trabalha neste tutorial e inclui a vinculação de dados para o modelo de vista. Ele importa o
OverviewViewModel
e então vincula a resposta do ViewModel
a um
TextView
. Em tutoriais posteriores, você substitui a vista de texto por uma grade de imagens
em um RecyclerView
.
Os dados imobiliários da Mars são armazenados em um servidor web, como um serviço web REST. Os serviços da Web que usam a arquitetura REST são construídos usando componentes e protocolos da Web padrão.
Você faz uma solicitação a um serviço da web de maneira padronizada por meio de URIs. O URL da web familiar é, na verdade, um tipo de URI e ambos são usados alternadamente ao longo deste curso. Por exemplo, no aplicativo desta lição, você recupera todos os dados do seguinte servidor:
https://android-kotlin-fun-mars-server.appspot.com
Se você digitar a seguinte URL em seu navegador, obterá uma lista de todas as propriedades imobiliárias disponíveis em Marte!
https://android-kotlin-fun-mars-server.appspot.com/realestate
A resposta de um serviço da web é comumente formatada em JSON, um formato de intercâmbio para representar dados estruturados. Você aprenderá mais sobre JSON na próxima tarefa, mas a breve explicação é que um objeto JSON é uma coleção de pares de valor-chave, às vezes chamados de dicionário, um mapa de hash ou uma matriz associativa. Uma coleção de objetos JSON é um array JSON, e é o array que você recebe como resposta de um serviço da web.
Para colocar esses dados no aplicativo, seu aplicativo precisa estabelecer uma conexão de rede e se comunicar com esse servidor e, em seguida, receber e analisar os dados de resposta em um formato que o aplicativo possa usar. Neste tutorial, você usa uma biblioteca cliente REST chamada Retrofit para fazer essa conexão.
dependencies
, adicione estas linhas para as bibliotecas de retrofit: implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
Observe que os números da versão são definidos separadamente no arquivo do projeto Gradle. A primeira
dependência é para a própria biblioteca Retrofit 2 e a segunda dependência é para o conversor escalar
Retrofit. Este conversor permite que o Retrofit retorne o resultado JSON como uma String
. As
duas bibliotecas funcionam juntas.
Muitas bibliotecas de terceiros, incluindo Retrofit2, usam recursos da linguagem Java 8. O plug-in do
Android para Gradle fornece suporte integrado para o uso de determinados recursos da linguagem Java 8. Para
usar esses recursos integrados, atualize o arquivo build.gradle
do módulo, conforme mostrado
abaixo:
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
O retrofit cria uma API de rede para o aplicativo com base no conteúdo do serviço da web. Ele busca dados do serviço da web e os encaminha por meio de uma biblioteca conversora separada que sabe como decodificar os dados e retorná-los na forma de objetos úteis. O retrofit inclui suporte integrado para formatos de dados da web populares, como XML e JSON. Em última análise, o retrofit cria a maior parte da camada de rede, incluindo detalhes críticos, como a execução de solicitações em threads de fundo.
A classe MarsApiService
contém a camada de rede do aplicativo; ou seja, esta é a API que seu
ViewModel
usará para se comunicar com o serviço da web. Esta é a classe em que você
implementará a API do serviço Retrofit.
app/java/network/MarsApiService.kt
. No momento, o arquivo contém apenas um objeto: Uma
constante para a URL base do serviço da web. private const val BASE_URL =
"https://android-kotlin-fun-mars-server.appspot.com"
retrofit2.Retrofit
e retrofit2.converter.scalars.ScalarsConverterFactory
quando
solicitado.private val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(BASE_URL)
.build()
O retrofit precisa de, pelo menos, dois argumentos disponíveis para construir uma API de serviços da web: O
URI básico para o serviço da web e uma fábrica de conversores. O conversor informa ao Retrofit o que fazer
com os dados que recebe do serviço da web. Nesse caso, você deseja que o Retrofit busque uma resposta JSON
do serviço da web e a retorne como uma String
. O retrofit tem um ScalarsConverter
que suporta strings e outros tipos primitivos, então você chama addConverterFactory()
no
construtor com uma instância de ScalarsConverterFactory
. Finalmente, você chama
build()
para criar o objeto Retrofit.
retrofit2.http.GET
e
retrofit2.Call
quando solicitado. interface MarsApiService {
@GET("realestate")
fun getProperties():
Call<String>
}
No momento, o objetivo é obter a string de resposta JSON do serviço da web, e você somente precisa de um
método para fazer isso: getProperties()
. Para informar ao Retrofit o que esse método deve
fazer, use uma anotação @GET
e especifique o caminho, ou ponto de extremidade, para esse método
de serviço da web. Neste caso, o ponto de extremidade é chamado de realestate
. Quando o método
getProperties()
é chamado, o Retrofit anexa o ponto de extremidade realestate
ao
URL base (que você definiu no construtor Retrofit) e cria uma Call
objeto. Esse objeto
Call
é usado para iniciar a solicitação.
MarsApiService
, defina um objeto público chamado MarsApi
para inicializar o serviço Retrofit. object MarsApi {
val retrofitService : MarsApiService by lazy {
retrofit.create(MarsApiService::class.java) }
}
O método Retrofit create()
cria o próprio serviço Retrofit com a interface
MarsApiService
. Como essa chamada é cara e o aplicativo somente precisa de uma instância de
serviço de Retrofit, você expõe o serviço ao restante do aplicativo usando um objeto público chamado
MarsApi
e inicializa vagarosamente o serviço de Retrofit lá. Agora que toda a configuração está
feita, cada vez que seu aplicativo chamar MarsApi.retrofitService
, ele obterá um objeto
Retrofit singleton que implementa MarsApiService
.
app/java/overview/OverviewViewModel.kt
. Role para baixo até o método
getMarsRealEstateProperties()
. private fun getMarsRealEstateProperties() {
_response.value = "Set the Mars API Response here!"
}
Este é o método em que você chamará o serviço Retrofit e tratará a string JSON retornada. No momento, há apenas um espaço reservado de string para a resposta.
getMarsRealEstateProperties()
, adicione o código mostrado abaixo. Importe
retrofit2.Callback
e com.example.android.marsrealestate.network.MarsApi
quando
solicitado. MarsApi.retrofitService.getProperties()
retorna um objeto
Call
. Então você pode chamar enqueue()
naquele objeto para iniciar a
solicitação de rede em uma thread de segundo plano. MarsApi.retrofitService.getProperties().enqueue(
object: Callback<String> {
})
object
, que está sublinhada em vermelho. Selecione Code >
Implement methods. Selecione onResponse()
e onFailure()
na lista.
override fun onFailure(call: Call<String>, t: Throwable) {
TODO("not implemented")
}
override fun onResponse(call: Call<String>,
response: Response<String>) {
TODO("not implemented")
}
onFailure()
, exclua o TODO e defina _response
para uma mensagem de falha,
conforme mostrado abaixo. A _response
é uma string LiveData
que determina o que
é mostrado na vista de texto. Cada estado precisa atualizar a _response
LiveData
.onFailure()
é chamado quando a resposta
do serviço da web falha. Para esta resposta, defina o status _response
para
"Failure: "
concatenado com a mensagem do argumento Throwable
.
override fun onFailure(call: Call<String>, t: Throwable) {
_response.value = "Failure: " + t.message
}
onResponse()
, exclua o TODO e defina _response
para o corpo da resposta. O
retorno de chamada onResponse()
é chamado quando a solicitação é bem-sucedida e o serviço da
web retorna uma resposta. override fun onResponse(call: Call<String>,
response: Response<String>) {
_response.value = response.body()
}
Process: com.example.android.marsrealestate, PID: 10646 java.lang.SecurityException: Permission denied (missing INTERNET permission?)
A mensagem de erro informa que seu aplicativo pode não ter a permissão INTERNET
. Conectar-se à
Internet apresenta questões de segurança, razão pela qual os aplicativos não possuem conectividade com a
Internet por padrão. Você precisa informar explicitamente ao Android que o aplicativo precisa de acesso à
internet.
app/manifests/AndroidManifest.xml
. Adicione esta linha antes da etiqueta
<application>
:<uses-permission android:name="android.permission.INTERNET" />
Agora você está recebendo uma resposta JSON do serviço da web Mars, o que é um ótimo começo. Mas o que você realmente precisa são objetos Kotlin, não uma grande string JSON. Há uma biblioteca chamada Moshi, que é um analisador JSON do Android que converte uma string JSON em objetos Kotlin. Retrofit tem um conversor que funciona com Moshi, então é uma ótima biblioteca para seus propósitos aqui.
Nesta tarefa, você usa a biblioteca Moshi com Retrofit para analisar a resposta JSON do serviço da web em objetos úteis Mars Property Kotlin. Você altera o aplicativo para que, em vez de exibir o JSON bruto, o aplicativo exiba o número de Propriedades de Marte retornadas.
$ version_moshi
é definido separadamente no arquivo Gradle de nível de
projeto. Essa dependência adiciona suporte para a biblioteca JSON Moshi com suporte Kotlin. implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"
dependencies
:implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
converter-moshi
:implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
Um exemplo de entrada da resposta JSON que você obtém do serviço da web se parece com isto:
[{"price":450000,
"id":"424906",
"type":"rent",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"},
...]
A resposta JSON mostrada acima é uma matriz, indicada pelos colchetes. A matriz contém objetos JSON, que são
cercados por chaves. Cada objeto contém um conjunto de pares nome-valor, separados por dois pontos. Os nomes
estão entre aspas. Os valores podem ser números ou strings, e as strings também estão entre aspas. Por
exemplo, o price
para esta propriedade é $ 450.000 e o img_src
é um URL, que é a
localização do arquivo de imagem no servidor.
No exemplo acima, observe que cada entrada de propriedade de Marte tem estes pares de chave e valor JSON:
price
: O preço da propriedade de Marte, como um número.id
: O ID da propriedade, como uma string.type
: "rent"
ou "buy"
.img_src
: O URL da imagem como uma string. Moshi analisa esses dados JSON e os converte em objetos Kotlin. Para fazer isso, ele precisa ter uma classe de dados Kotlin para armazenar os resultados analisados, portanto, a próxima etapa é criar essa classe.
app/java/network/MarsProperty.kt
.MarsProperty
existente pelo seguinte código: data class MarsProperty(
val id: String, val img_src: String,
val type: String,
val price: Double
)
Observe que cada uma das variáveis na classe MarsProperty
corresponde a um nome de chave no
objeto JSON. Para combinar os tipos no JSON, você usa objetos String
para todos os valores,
exceto price
, que é um Double
. Um Double
pode ser usado para
representar qualquer número JSON.
Quando Moshi analisa o JSON, ele combina as chaves por nome e preenche os objetos de dados com os valores apropriados.
img_src
pela linha mostrada abaixo. Importe
com.squareup.moshi.Json
quando solicitado.@Json(name = "img_src") val imgSrcUrl: String,
Às vezes, os nomes de chave em uma resposta JSON podem tornar as propriedades Kotlin confusas ou podem não
corresponder ao seu estilo de codificação - por exemplo, no arquivo JSON, a chave img_src
usa
um sublinhado, enquanto as propriedades Kotlin normalmente usam superior e letras minúsculas ("camel case").
Para usar nomes de variáveis em sua classe de dados que sejam diferentes dos nomes de chave na resposta
JSON, use a anotação @Json
. Neste exemplo, o nome da variável na classe de dados é
imgSrcUrl
. A variável é mapeada para o atributo JSON img_src
usando
@Json(name = "img_src")
.
Com a classe de dados MarsProperty
no lugar, agora você pode atualizar a API da rede e o
ViewModel
para incluir os dados Moshi.
network/MarsApiService.kt
. Você pode ver erros de classe ausente para
ScalarsConverterFactory
. Isso se deve à alteração de dependência do Retrofit que você fez na
Etapa 1. Você corrige esses erros em breve. com.squareup.moshi.Moshi
e
com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
quando solicitado.private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
Semelhante ao que você fez com o Retrofit, aqui você cria um objeto moshi
usando o construtor
Moshi. Para que as anotações de Moshi funcionem corretamente com Kotlin, adicione a
KotlinJsonAdapterFactory
e chame build()
.
MoshiConverterFactory
em vez da
ScalarConverterFactory
e passe a instância moshi
que você acabou de criar.
Importe retrofit2.converter.moshi.MoshiConverterFactory
quando solicitado. private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
ScalarConverterFactory
também.Código para excluir:
import retrofit2.converter.scalars.ScalarsConverterFactory
MarsApiService
para que o Retrofit retorne uma lista de objetos
MarsProperty
, em vez de retornar Call<String>
.interface MarsApiService {
@GET("realestate")
fun getProperties():
Call<List<MarsProperty>>
}
OverviewViewModel.kt
. Role para baixo até a chamada de
getProperties().enqueue()
no método getMarsRealEstateProperties()
.enqueue()
de Callback<String>
para
Callback<List<MarsProperty>>
. Importe
com.example.android.marsrealestate.network.MarsProperty
quando solicitado.MarsApi.retrofitService.getProperties().enqueue(
object: Callback<List<MarsProperty>> {
onFailure()
, altere o argumento de Call<String>
para
Call<List<MarsProperty>>
:override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {
onResponse()
: override fun onResponse(call: Call<List<MarsProperty>>,
response: Response<List<MarsProperty>>) {
onResponse()
, substitua a atribuição existente para
_response.value
pela atribuição mostrada abaixo. Como response.body()
agora é
uma lista de objetos MarsProperty
, o tamanho dessa lista é o número de propriedades que
foram analisadas. Esta mensagem de resposta imprime esse número de propriedades: _response.value =
"Success: ${response.body()?.size} Mars properties retrieved"
Agora o serviço da API Retrofit está em execução, mas usa um retorno de chamada com dois métodos de retorno
de chamada que você teve que implementar. Um método trata do sucesso e outro trata da falha, e o resultado
da falha relata exceções. Seu código seria mais eficiente e fácil de ler se você pudesse usar corrotinas com
tratamento de exceção, em vez de usar callbacks. Nesta tarefa, você converte seu serviço de rede e o
ViewModel
para usar corrotinas.
MarsApiService
, torne getProperties()
uma função de suspensão. Altere
Call<List<MarsProperty>>
para List<MarsProperty>
. O método
getProperties()
se parece com isto:@GET("realestate")
suspend fun getProperties(): List<MarsProperty>
OverviewViewModel.kt
, exclua todo o código dentro de
getMarsRealEstateProperties()
. Você usará corrotinas aqui em vez da chamada para
enqueue()
e os callbacks onFailure()
e onResponse()
. getMarsRealEstateProperties()
, inicie a co-rotina usando
viewModelScope.
viewModelScope.launch {
}
Um ViewModelScope
é o escopo de co-rotina integrado definido para cada
ViewModel
em seu aplicativo. Qualquer corrotina lançada neste escopo é automaticamente
cancelada se ViewModel
for limpo.
try
/catch
para lidar com as
exceções: try {
} catch (e: Exception) {
}
try {}
, chame getProperties()
no objeto
retrofitService
:val listResult = MarsApi.retrofitService.getProperties()
Chamar getProperties()
do serviço MarsApi
cria e inicia a chamada de rede em um
thread de segundo plano.
try {}
, atualize a mensagem de resposta para a resposta
bem-sucedida:_response.value =
"Success: ${listResult.size} Mars properties retrieved"
catch {}
, trate com a resposta de falha: _response.value = "Failure: ${e.message}"
O método getMarsRealEstateProperties()
completo agora se parece com isto:
private fun getMarsRealEstateProperties() {
viewModelScope.launch {
try {
val listResult = MarsApi.retrofitService.getProperties()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
}
}
Projeto Android Studio: MarsRealEstateNetwork
ScalarsConverter
trata os dados do
serviço da web como uma String
ou outra primitiva. "android.permission.INTERNET"
no manifesto do Android. @Json
e o nome da chave JSON. Documentação para desenvolvimento em Android:
Documentação Kotlin:
De outros:
Esta seção lista as possíveis tarefas de casa para os alunos que estão trabalhando neste tutorial como parte de um curso ministrado por um instrutor.
Quais são os dois argumentos que o Retrofit precisa para construir uma API de serviços da web?
▢ O URI básico para o serviço da web e uma consulta GET
.
▢ O URI básico para o serviço da web e uma fábrica de conversores.
▢ Uma conexão de rede com o serviço da Web e um token de autorização.
▢ Uma fábrica de conversores e um analisador para a resposta.
Qual é o propósito da biblioteca Moshi?
▢ Para obter dados de um serviço da web.
▢ Para interagir com o Retrofit para fazer uma solicitação de serviço web.
▢ Para analisar uma resposta JSON de um serviço da web em objetos de dados Kotlin.
▢ Para renomear objetos Kotlin para corresponder às chaves na resposta JSON.
Comece a próxima lição:
Para obter enlaces para outros tutoriais neste curso, consulte a página de destino dos tutoriais Fundamentos de Android em Kotlin.