La mayoría de las apps de Android en el mercado se conectan a Internet para realizar algunas operaciones de red, como recuperar mensajes de correo electrónico, mensajes o información similar de un servidor backend. Gmail, YouTube y Google Fotos son algunas apps de ejemplo que se conectan a Internet para mostrar los datos del usuario.
En este codelab, usarás bibliotecas desarrolladas de código abierto para compilar la capa de red y obtener datos de un servidor backend. Esto simplifica enormemente la obtención de datos y también ayuda a la app a cumplir con las prácticas recomendadas de Android, como la realización de las operaciones en un subproceso en segundo plano. También actualizarás la interfaz de usuario de la app si la conexión a Internet es lenta o no está disponible. Esto mantendrá al usuario informado de cualquier problema de conectividad de red.
Conocimientos que ya deberías tener
- Cómo crear y usar fragmentos
- Cómo usar los componentes de la arquitectura de Android
ViewModel
yLiveData
- Cómo agregar dependencias en un archivo de Gradle
Qué aprenderás
- Qué es un servicio web REST
- Cómo usar la biblioteca Retrofit para conectarte a un servicio web REST en Internet y obtener una respuesta
- Cómo usar la biblioteca Moshi para analizar la respuesta JSON en un objeto de datos
Actividades
- Modificarás una app de inicio para realizar una solicitud a la API de servicio web y manejar la respuesta.
- Implementarás una capa de red para tu app usando la biblioteca Retrofit.
- Analizarás la respuesta JSON del servicio web en los objetos
LiveData
de tu app con la biblioteca Moshi. - Usarás la compatibilidad de Retrofit para las corrutinas a fin de simplificar el código.
Requisitos
- Una computadora que tenga Android Studio instalado
- Código inicial para la app de MarsPhotos
En esta ruta de aprendizaje, trabajarás con una app de inicio llamada MarsPhotos, que muestra imágenes de la superficie de Marte. Esta app se conecta a un servicio web para recuperar y mostrar las fotos de Marte. Las imágenes son fotografías reales de Marte capturadas por los Mars rovers de la NASA. A continuación, se muestra la captura de pantalla de la app final, que contiene una cuadrícula de imágenes de miniaturas de propiedades compilada con un RecyclerView
.
La versión de la app que compilas en este codelab no tendrá muchos elementos visuales llamativos, ya que se centra en la parte de la capa de red de la app para conectarse a Internet y descargar los datos de propiedad sin procesar con un servicio web. Para garantizar que los datos se recuperen y analicen correctamente, solo mostrarás el número de fotos recibidas del servidor backend en una vista de texto:
Descarga el código de inicio
Este codelab te brinda el código de inicio para que lo extiendas con funciones que se explican en este codelab. Es posible que el código de inicio incluya código que te resulte conocido y desconocido por los codelabs anteriores. Aprenderás más sobre el código desconocido en codelabs posteriores.
Si usas el código de inicio de GitHub, ten en cuenta que el nombre de la carpeta es android-basics-kotlin-mars-photos-app
. Selecciona esta carpeta cuando abras el proyecto en Android Studio.
A fin de obtener el código necesario para este codelab y abrirlo en Android Studio, haz lo siguiente:
Obtén el código
- Haz clic en la URL proporcionada. Se abrirá la página de GitHub del proyecto en un navegador.
- En esa página, haz clic en el botón Code, que abre un cuadro de diálogo.
- En el cuadro de diálogo, haz clic en el botón Download ZIP para guardar el proyecto en tu computadora. Espera a que se complete la descarga.
- Ubica el archivo en tu computadora (probablemente en la carpeta Descargas).
- Haz doble clic en el archivo ZIP para descomprimirlo. Se creará una carpeta nueva con los archivos del proyecto.
Abre el proyecto en Android Studio
- Inicia Android Studio.
- En la ventana Welcome to Android Studio, haz clic en Open an existing Android Studio project.
Nota: Si Android Studio ya está abierto, selecciona la opción de menú File > New > Import Project.
- En el cuadro de diálogo Import Project, navega hasta donde se encuentra la carpeta de proyecto descomprimido (probablemente en Descargas).
- Haz doble clic en la carpeta del proyecto.
- Espera a que Android Studio abra el proyecto.
- Haz clic en el botón Run para compilar y ejecutar la app. Asegúrate de que funcione como se espera.
- Explora los archivos del proyecto en la ventana de herramientas Project para ver cómo se implementó la app.
Ejecuta el código de inicio
- Abre el proyecto descargado en Android Studio. El nombre de la carpeta del proyecto es
android-basics-kotlin-mars-photos-app
. La estructura de carpetas del código de inicio debería verse como se muestra más abajo. - En el panel de Android, expande app > java. Observa que la app tiene una carpeta de paquete llamada
overview
. Esta es la capa de la IU de la app.
- Ejecuta la app. Cuando compilas y ejecutas la app, deberías ver la siguiente pantalla con un texto de marcador de posición en el centro. Al final del codelab, actualizarás este texto de marcador de posición con la cantidad de fotos recuperadas.
- Explora los archivos para comprender el código de inicio. Para los archivos de diseño, puedes usar la opción Split en la esquina superior derecha a fin de obtener una vista previa del diseño y el XML al mismo tiempo.
Explicación del código de inicio
En esta tarea, te familiarizarás con la estructura del proyecto. A continuación, se explican los archivos y las carpetas importantes del proyecto.
OverviewFragment:
- Este es el fragmento que se muestra dentro de
MainActivity
. El texto del marcador de posición que viste en el paso anterior se muestra en este fragmento. - En el siguiente codelab, este fragmento mostrará los datos recibidos del servidor backend de fotos de Marte.
- Esta clase contiene una referencia al objeto
OverviewViewModel
. OverviewFragment
tiene una funciónonCreateView()
que aumenta el diseño defragment_overview
con la vinculación de datos. Luego, establece el propietario del ciclo de vida para la vinculación y establece la variableviewModel
en el objeto de vinculación.- Como el propietario del ciclo de vida está asignado, se buscarán cambios automáticamente en cualquier
LiveData
utilizado en la vinculación de datos, y la IU se actualizará según corresponda.
OverviewViewModel:
- Este es el modelo de vista correspondiente para
OverviewFragment
. - Esta clase contiene una propiedad
MutableLiveData
llamada_status
junto con su propiedad de copia de seguridad. Cuando se actualiza el valor de esta propiedad, se actualiza el texto del marcador de posición que se muestra en la pantalla. - El método
getMarsPhotos()
actualiza la respuesta del marcador de posición. Más adelante en el codelab, usarás esto para mostrar los datos obtenidos del servidor. El objetivo de este codelab es que actualicesstatus
LiveData
dentro deViewModel
con datos reales que obtuviste de Internet.
res/layout/fragment_overview.xml
:
- Este diseño se configuró para usar la vinculación de datos y consiste en una sola
TextView
. - Declara una variable
OverviewViewModel
y, luego, vincula elstatus
deViewModel
aTextView
.
MainActivity.kt
: La única tarea de esta actividad es cargar el diseño de la actividad, activity_main
.
layout/activity_main.xml:
es el diseño principal de la actividad con un solo FragmentContainerView
que apunta a fragment_overview
. Se creará una instancia del fragmento de descripción general cuando se inicie la app.
En este codelab, crearás una capa para el servicio de red que se comunica con el servidor backend y recupera los datos requeridos. Para implementar esto, usarás una biblioteca de terceros llamada Retrofit. Obtendrás más información sobre el tema más adelante. ViewModel
se comunica directamente con esa capa de red y el resto de la app es transparente para esta implementación.
OverviewViewModel
es responsable de realizar la llamada de red para obtener los datos de las fotos de Marte. En ViewModel
, usas LiveData
con vinculación de datos optimizada para ciclos de vida a fin de actualizar la IU de la app cuando cambian los datos.
Los datos de las fotos de Marte se almacenan en un servidor web. Para obtener estos datos en tu app, debes establecer una conexión y comunicarte con el servidor en Internet.
La mayoría de los servidores web ejecutan servicios web con una arquitectura web sin estado común conocida como REST o REpresentational State Transfer (transferencia de estado representacional). Los servicios web que ofrecen esta arquitectura se conocen como servicios RESTful.
Se realizan solicitudes a los servicios web RESTful de manera estandarizada a través de URI. Un URI (identificador uniforme de recursos) identifica un recurso en el servidor por nombre, sin indicar su ubicación ni cómo acceder a él. Por ejemplo, en la app de esta lección, recuperas las URL de la imagen usando el siguiente URI del servidor (este servidor aloja las fotos y los inmuebles de Marte):
android-kotlin-fun-mars-server.appspot.com
Un localizador uniforme de recursos (URL) es un URI que especifica los medios para modificar u obtener la representación de un recurso, es decir, especifica su mecanismo de acceso principal y la ubicación de la red.
Por ejemplo:
La siguiente URL obtiene una lista de todas las propiedades de bienes raíces disponibles en Marte.
https://android-kotlin-fun-mars-server.appspot.com/realestate
En la siguiente URL, se obtiene una lista de fotos de Marte:
https://android-kotlin-fun-mars-server.appspot.com/photos
Estas URL hacen referencia a un recurso identificado como /realestate o /photos, que se puede obtener a través del Protocolo de transferencia de hipertexto (http:) de la red. En este codelab, usarás el extremo /photos.
Solicitud de servicio web
Cada solicitud de servicio web contiene un URI y se transfiere al servidor mediante el mismo protocolo HTTP que usan los navegadores web, como Chrome. Las solicitudes HTTP contienen una operación para indicarle al servidor cómo proseguir.
Entre las operaciones comunes de HTTP se incluyen las siguientes:
- GET para recuperar los datos del servidor
- POST o PUT para agregar datos nuevos al servidor, crearlos o actualizarlos.
- DELETE para borrar datos del servidor
Tu app realizará una solicitud GET HTTP al servidor para la información de fotos de Marte y, luego, el servidor mostrará una respuesta a nuestra app que incluye URL de imágenes.
La respuesta de un servicio web suele tener uno de los formatos web comunes, como XML o JSON, para representar datos estructurados en pares clave-valor. Obtendrás más información sobre JSON en la siguiente tarea.
En esta tarea, establecerás una conexión de red con el servidor, te comunicarás con el servidor y recibirás una respuesta JSON. Utilizarás un servidor backend que ya está escrito. En este codelab, usarás la biblioteca Retrofit, una biblioteca de terceros para comunicarte con el servidor backend.
Bibliotecas externas
Las bibliotecas externas o las bibliotecas de terceros son como las extensiones de las API de Android principales. Son, en su mayoría, de código abierto, y están desarrolladas por la comunidad y mantenidas por las contribuciones colectivas de la enorme comunidad de Android de todo el mundo. Esto permite a los desarrolladores de Android como tú crear mejores apps.
Biblioteca Retrofit
La biblioteca Retrofit que utilizarás en este codelab para hablar con el servicio web RESTful de Marte es un buen ejemplo de una biblioteca bien mantenida. Para saberlo, en su página de GitHub, consulta los problemas abiertos (algunos de ellos son solicitudes de funciones) y los problemas cerrados. Si los desarrolladores resuelven los problemas y responden a las solicitudes de funciones con frecuencia, esto implica que la biblioteca tiene mantenimiento regular y es una buena candidata para usarla en la app. También tienen una página de documentación de Retrofit.
La biblioteca Retrofit se comunicará con el backend. Crea URI para el servicio web según los parámetros que le pasamos. Verás más información al respecto en secciones posteriores.
Cómo agregar dependencias de Retrofit
Gradle de Android te permite agregar bibliotecas externas a tu proyecto. Además de la dependencia de biblioteca, también debes incluir el repositorio en el que se aloja la biblioteca. Las bibliotecas de Google como ViewModel y LiveData de la biblioteca Jetpack se alojan en el repositorio de Google. La mayoría de las bibliotecas de la comunidad se realizan en JCenter, como Retrofit.
- Abre el archivo de nivel superior
build.gradle(Project: MarsPhotos)
del proyecto. Observa los repositorios que aparecen debajo del bloquerepositories
. Deberías ver dos repositorios:google()
yjcenter()
.
repositories {
google()
jcenter()
}
- Abre el archivo de Gradle de nivel de módulo,
build.gradle (Module: MarsPhots.app)
. - En la sección
dependencies
, agrega estas líneas para las bibliotecas Retrofit:
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with Moshi Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
La primera dependencia es para la biblioteca Retrofit2 y la segunda es para el conversor escalar de Retrofit. Este conversor permite que Retrofit muestre el resultado JSON como String
. Las dos bibliotecas funcionan juntas.
- Haz clic en Sync Now para volver a compilar el proyecto con las dependencias nuevas.
Cómo agregar compatibilidad con funciones del lenguaje Java 8
Muchas bibliotecas de terceros, como Retrofit2, usan funciones del lenguaje Java 8. El complemento de Android para Gradle tiene compatibilidad integrada para usar determinadas funciones del lenguaje Java 8.
- Para usar las funciones integradas, necesitas el siguiente código en el archivo
build.gradle
de tu módulo. Ya completaste este paso. Asegúrate de que el siguiente código esté presente en tubuild.gradle(Module: MarsPhotos.app).
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
Usarás la biblioteca Retrofit para hablar con el servicio web de Marte y mostrar la respuesta JSON sin procesar como String
. El marcador de posición TextView
mostrará la string de respuesta JSON que aparece o un mensaje que indica un error de conexión.
Retrofit crea una API de red para la app basada en el contenido del servicio web. Recupera datos del servicio web y los enruta a través de una biblioteca de conversor independiente que sabe cómo decodificar los datos y mostrarlos en forma de objetos como String
. Retrofit incluye compatibilidad integrada para formatos de datos populares, como XML y JSON. Retrofit crea el código para llamar y consumir este servicio, incluidos los detalles críticos, como la ejecución de solicitudes en subprocesos en segundo plano.
En esta tarea, agregarás una capa de red a tu proyecto MarsPhotos que tu ViewModel
usará para comunicarse con el servicio web. Implementarás la API del servicio de Retrofit con los siguientes pasos.
- Crea una capa de red: la clase
MarsApiService
. - Crea un objeto de Retrofit con la URL de base y la fábrica del conversor.
- Crear una interfaz que explique cómo habla Retrofit con nuestro servidor web.
- Crea un servicio de Retrofit y expón la instancia del servicio de la API al resto de la app.
Implementa los pasos anteriores:
- Crea un paquete nuevo llamado "network". En el panel de tu proyecto de Android, haz clic con el botón derecho en el paquete,
com.example.android.marsphotos
. Selecciona New > Package. En la ventana emergente, agrega network al final del nombre del paquete sugerido. - Crea un nuevo archivo de Kotlin debajo del nuevo paquete network. Asígnale el nombre
MarsApiService.
. - Abre
network/MarsApiService.kt
. Agrega la siguiente constante para la URL de base del servicio web.
private const val BASE_URL =
"https://android-kotlin-fun-mars-server.appspot.com"
- Justo debajo de esa constante, agrega un compilador de Retrofit para compilar y crear un objeto Retrofit.
private val retrofit = Retrofit.Builder()
Importa retrofit2.Retrofit
cuando se te solicite.
- Retrofit requiere el URI base para el servicio web y una fábrica de conversión para crear una API de servicios web. El conversor le indica a Retrofit qué hacer con los datos que obtiene del servicio web. En este caso, Retrofit tendría que recuperar una respuesta JSON del servicio web y mostrarla como
String
. Retrofit tiene unScalarsConverter
que admite strings y otros tipos primitivos, por lo que llamas aaddConverterFactory()
en el compilador con una instancia deScalarsConverterFactory
.
private val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
Importa retrofit2.converter.scalars.ScalarsConverterFactory
cuando se te solicite.
- Agrega el URI de base para el servicio web con el método
baseUrl()
. Por último, llama abuild()
para crear el objeto Retrofit.
private val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(BASE_URL)
.build()
- Debajo de la llamada al compilador de Retrofit, define una interfaz llamada
MarsApiService
, que define cómo Retrofit se comunica con el servidor web mediante solicitudes HTTP.
interface MarsApiService {
}
- Dentro de la interfaz
MarsApiService
, agrega una función llamadagetPhotos()
para obtener la string de respuesta del servicio web.
interface MarsApiService {
fun getPhotos()
}
- Usa la anotación
@GET
para indicar a Retrofit que es una solicitud GET y especifica el extremo para ese método de servicio web. En este caso, el extremo se llamaphotos
. Como se mencionó en la tarea anterior, usarás el extremo /photos en este codelab.
interface MarsApiService {
@GET("photos")
fun getPhotos()
}
Importa retrofit2.http.GET
cuando se solicite.
- Cuando se invoca el método
getPhotos()
, Retrofit agrega el extremophotos
a la URL de base (que definiste en el compilador de Retrofit) que se usó para iniciar la solicitud. Agrega un tipo de datos que se muestra de la función aString
.
interface MarsApiService {
@GET("photos")
fun getPhotos(): String
}
Declaraciones de objetos
En Kotlin, las declaraciones de objetos se usan para declarar objetos singleton. El patrón de singleton garantiza que una y solo una instancia de un objeto se cree, tiene un punto de acceso global a ese objeto. La inicialización de la declaración del objeto es segura para los subprocesos y se realiza durante el primer acceso.
Kotlin facilita la declaración de singleton. A continuación, se muestra un ejemplo de una declaración de objeto y su acceso. La declaración del objeto siempre tiene un nombre que sigue a la palabra clave object
.
Ejemplo:
// Object declaration
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
// To refer to the object, use its name directly.
DataProviderManager.registerDataProvider(...)
La función call to create() en un objeto Retrofit es costosa, y la app necesita solo una instancia del servicio de la API de Retrofit. Por lo tanto, expones el servicio al resto de la app con la declaración de objeto.
- Fuera de la declaración de la interfaz de
MarsApiService
, define un objeto público llamadoMarsApi
para inicializar el servicio de Retrofit. Este es el objeto singleton público al que se puede acceder desde el resto de la app.
object MarsApi {
}
- Dentro de la declaración del objeto
MarsApi
, agrega una propiedad de objeto de Retrofit inicializada de forma diferida y con el nombreretrofitService
del tipoMarsApiService
. Realizas esta inicialización diferida para asegurarte de que se inicialice en su primer uso. Solucionarás el error en los pasos siguientes.
object MarsApi {
val retrofitService : MarsApiService by lazy {
}
}
- Inicializa la variable
retrofitService
usando el métodoretrofit.create()
con la interfazMarsApiService
.
object MarsApi {
val retrofitService : MarsApiService by lazy {
retrofit.create(MarsApiService::class.java) }
}
Se completó la configuración de Retrofit. Cada vez que tu app llama a MarsApi.retrofitService
, el emisor tendrá el mismo acceso a un objeto singleton de Retrofit que implementa MarsApiService
, que se crea en el primer acceso. En la próxima tarea, usarás el objeto Retrofit que implementaste.
Llama al servicio web en OverviewViewModel
En este paso, implementarás un método getMarsPhotos()
que llama al servicio de actualización y, luego, maneja la string JSON que se muestra.
ViewModelScope
Un ViewModelScope
es el alcance integrado de corrutinas definido para cada ViewModel
en tu app. Si se borra ViewModel
, se cancela automáticamente cualquier corrutina iniciada en este alcance.
Usarás ViewModelScope
para iniciar la corrutina y realizar la llamada de red Retrofit en segundo plano.
- En
MarsApiService
, haz quegetPhotos()
sea una función de suspensión. Así que puedes llamar a este método desde una corrutina.
@GET("photos")
suspend fun getPhotos(): String
- Abre
overview/OverviewViewModel
. Desplázate hacia abajo hasta el métodogetMarsPhotos()
. Borra la línea que establece la respuesta de estado en"Set the Mars API Response here!".
. El métodogetMarsPhotos()
debería estar vacío ahora.
private fun getMarsPhotos() {
}
- Dentro de
getMarsPhotos()
, inicia la corrutina medianteviewModelScope.launch
.
private fun getMarsPhotos() {
viewModelScope.launch {
}
}
Importa androidx.lifecycle.
viewModelScope
y kotlinx.coroutines.launch
cuando se te solicite.
- Dentro de
viewModelScope
, usa el objeto singletonMarsApi
para llamar al métodogetPhotos()
desde la interfazretrofitService
. Guarda la respuesta que se muestra en unval
llamadolistResult
.
viewModelScope.launch {
val listResult = MarsApi.retrofitService.getPhotos()
}
Importa com.example.android.marsphotos.network.MarsApi
cuando se te solicite.
- Asigna el resultado que acabamos de recibir del servidor backend a
_status.value.
val listResult = MarsApi.retrofitService.getProperties()
_status.value = listResult
- Ejecuta la app, ten en cuenta que la app se cierra de inmediato y puede mostrar una ventana emergente de error o no.
- En Android Studio, haz clic en la pestaña Logcat y observa el error en el registro, que comienza con una línea como la siguiente: "
------- beginning of crash
"
--------- beginning of crash 22803-22865/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher Process: com.example.android.marsphotos, PID: 22803 java.lang.SecurityException: Permission denied (missing INTERNET permission?) ...
Este mensaje de error indica que a la app le podrían faltar los permisos INTERNET
. Para solucionar este problema, agrega permisos de Internet a la app en la próxima tarea.
Permisos de Android
El objetivo de los permisos de Android es proteger la privacidad de un usuario de Android. Las apps para Android deben declarar o solicitar permisos a fin de acceder a datos sensibles del usuario, como contactos, registros de llamadas y ciertas funciones del sistema, como la cámara o Internet.
Para que tu app pueda acceder a Internet, necesita el permiso INTERNET
. La conexión a Internet presenta problemas de seguridad, por lo que las apps no tienen conexión a Internet de forma predeterminada. Debes declarar explícitamente que la aplicación necesita acceso a Internet. Esto se considera un permiso normal. Para obtener más información sobre los permisos de Android y sus tipos, consulta la documentación.
En este paso, tu app declara los permisos que requiere incluyendo etiquetas <uses-permission>
en el archivo AndroidManifest
.
- Abre
manifests/AndroidManifest.xml
. Agrega esta línea justo antes de la etiqueta<application>
:
<uses-permission android:name="android.permission.INTERNET" />
- Vuelve a compilar y ejecutar la app. Si tienes una conexión a Internet activa, deberías ver el texto JSON con datos relacionados a las fotos de Marte. Obtendrás más información sobre el formato JSON más adelante en el codelab.
- Presiona el botón Atrás en el dispositivo o emulador para cerrar la app.
- Activa el modo de avión en tu dispositivo o emulador para simular un error de conexión de red. Vuelve a abrir la app desde el menú Recientes o reinicia la app desde Android Studio.
- Haz clic en la pestaña Logcat en Android Studio y observa la excepción fatal en el registro, que es similar a la siguiente:
3302-3302/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.android.marsphotos, PID: 3302 java.net.SocketTimeoutException: timeout ...
Este mensaje de error indica que la aplicación intentó conectarse y se agotó el tiempo de espera. Este tipo de excepciones son muy comunes en tiempo real. En el siguiente paso, aprenderás a manejar esas excepciones.
Manejo de excepciones
Las excepciones son errores que pueden ocurrir durante el tiempo de ejecución (no durante el tiempo de compilación) y finalizar la app de manera repentina sin notificar al usuario. Esto puede afectar la experiencia del usuario. El manejo de excepciones es un mecanismo mediante el cual puedes evitar que la app se cierre de forma repentina y manejar el problema de una forma sencilla.
El motivo de las excepciones podría ser tan simple como la división por cero o un error en la red. Estas excepciones son similares a la NumberFormatException
que aprendiste en un codelab anterior.
Ejemplos de posibles problemas durante la conexión a un servidor:
- La URL o el URI que se usaron en la API son incorrectos.
- El servidor no está disponible y la app no pudo conectarse a él.
- Hay un problema en la latencia de la red.
- Conexión a Internet baja o nula en el dispositivo.
No se pueden capturar estas excepciones durante el tiempo de compilación. Puedes usar un bloque try-catch
para manejar la excepción en el entorno de ejecución. Para obtener más información, consulta la documentación.
Sintaxis de ejemplo para el bloque try-catch
try {
// some code that can cause an exception.
}
catch (e: SomeException) {
// handle the exception to avoid abrupt termination.
}
Dentro del bloque try
, realizarás el código donde preves una excepción. En tu app, sería una llamada de red. En el bloque catch
, implementarás el código que impide la finalización repentina de la app. Si existe una excepción, se ejecutará el bloque catch
para recuperarse del error en lugar de cerrar la app de manera repentina.
- Abre
overview/OverviewViewModel.kt
. Desplázate hacia abajo hasta el métodogetMarsPhotos()
. Dentro del bloque de lanzamiento, agrega un bloquetry
alrededor de la llamadaMarsApi
para manejar las excepciones. Agrega el bloquecatch
después del bloquetry
:
viewModelScope.launch {
try {
val listResult = MarsApi.retrofitService.getPhotos()
_status.value = listResult
} catch (e: Exception) {
}
}
- Dentro del bloque
catch {}
, controla la respuesta de falla. Para mostrar el mensaje de error al usuario, configurae.message
como_status.
value
.
catch (e: Exception) {
_status.value = "Failure: ${e.message}"
}
- Vuelve a ejecutar la app con el modo de avión activado. La app no se cierra de forma repentina, pero muestra un mensaje de error.
- Desactiva el modo de avión en tu teléfono o emulador. Ejecuta y prueba tu app, asegúrate de que todo funcione bien y que puedas ver la string JSON.
JSON
Por lo general, los datos solicitados tienen un formato de datos común, como XML o JSON. Cada llamada muestra datos estructurados y tu app debe saber cuál es esa estructura para poder leer los datos de la respuesta.
Por ejemplo, en esta app, recuperarás los datos de este servidor: https:// android-kotlin-fun-mars-server.appspot.com/photos. Si ingresas esta URL en el navegador,
verás una lista de los ID y las URL de imagen de la superficie de Marte en un formato JSON.
Estructura de la respuesta JSON de muestra:
- La respuesta JSON es un array, que se indica con los corchetes. El array contiene objetos JSON.
- Los objetos JSON aparecen entre llaves.
- Cada objeto JSON contiene un conjunto de pares nombre-valor. El nombre y el valor están separados por dos puntos.
- Los nombres aparecen entre comillas.
- Los valores pueden ser números, strings, un valor booleano, un array, un objeto (JSON) o nulo.
Por ejemplo, img_src
es una URL, que es una string. Si pegas la URL en un navegador web, verás una imagen de la superficie de Marte.
Ahora obtienes una respuesta JSON del servicio web de Marte, que es un buen comienzo. Pero lo que necesitas son objetos Kotlin, no una string JSON grande. Hay una biblioteca externa llamada Moshi, que es un analizador de JSON de Android que convierte una string JSON en objetos Kotlin. Retrofit tiene un conversor que funciona con Moshi, por lo que es una excelente biblioteca para sus propósitos aquí.
En esta tarea, usarás la biblioteca Moshi con Retrofit para analizar la respuesta JSON del servicio web en objetos de Kotlin útiles que representen fotos de Marte. Cambiarás la app de modo que en lugar de mostrar el JSON sin procesar, muestre la cantidad de fotos de Marte que se muestran.
Agrega dependencias de la biblioteca Moshi
- Abre build.gradle (Module: app).
- En la sección de dependencias, agrega el código que se muestra a continuación para incluir la dependencia de Moshi. Esta dependencia agrega compatibilidad con la biblioteca JSON de Moshi compatible con Kotlin.
// Moshi
implementation 'com.squareup.moshi:moshi-kotlin:1.9.3'
- Busca las líneas del conversor de escalar Retrofit en el bloque
dependencies
y cambia estas dependencias para usarconverter-moshi
:
Reemplaza lo siguiente
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with Moshi Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
De esta forma
// Retrofit with Moshi Converter
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
- Haz clic en Sync Now para volver a compilar el proyecto con las dependencias nuevas.
Cómo implementar la clase de datos de Mars Photos
Una entrada de muestra de la respuesta JSON que obtienes del servicio web se ve de la siguiente manera, similar a lo que viste antes:
[{
"id":"424906",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"
},
...]
En el ejemplo anterior, observa que cada entrada de foto de Marte tiene estos pares clave-valor y valores JSON:
id
: El ID de la propiedad, como una string. Como se encierra en" "
, es del tipoString
, noInteger
.img_src
: La URL de la imagen como una string.
Moshi analiza estos datos JSON y los convierte en objetos Kotlin. Para ello, Moshi necesita tener una clase de datos de Kotlin a fin de almacenar los resultados analizados, por lo que en este paso crearás la clase de datos MarsPhoto
.
- Haz clic con el botón derecho en el paquete network y selecciona New > Kotlin File/Class.
- En la ventana emergente, selecciona Class y, luego, ingresa
MarsPhoto
como el nombre de la clase. Esto crea un archivo nuevo llamadoMarsPhoto.kt
en el paquetenetwork
. - Para hacer que
MarsPhoto
sea una clase de datos, agrega la palabra clavedata
antes de la definición de la clase. Cambia las llaves {} por paréntesis (). Esto deja un error, ya que las clases de datos deben tener al menos una propiedad definida.
data class MarsPhoto(
)
- Agrega las siguientes propiedades a la definición de la clase
MarsPhoto
.
data class MarsPhoto(
val id: String, val img_src: String
)
Observa que cada una de las variables de la clase MarsPhoto
corresponde a un nombre de clave en el objeto JSON. Para hacer coincidir los tipos en nuestra respuesta JSON específica, usas objetos String
para todos los valores.
Cuando Moshi analiza el JSON, busca las claves por nombre y completa los objetos de datos con los valores correspondientes.
Anotación de @Json
A veces, los nombres de clave en una respuesta JSON pueden hacer que las propiedades de Kotlin sean confusas o no coincidan con el estilo de codificación recomendado. Por ejemplo, en el archivo JSON, la clave img_src
usa un guion bajo, mientras que la convención de Kotlin para las propiedades usa letras mayúsculas y minúsculas.
Para usar nombres de variables en tu clase de datos que difieren de los nombres de clave en la respuesta JSON, usa la anotación @Json
. En este ejemplo, el nombre de la variable en la clase de datos es imgSrcUrl
. La variable puede asignarse al atributo JSON img_src
usando @Json(name = "img_src")
.
- Reemplaza la línea para la clave
img_src
con la línea que se muestra a continuación. Importacom.squareup.moshi.Json
cuando se solicite.
@Json(name = "img_src") val imgSrcUrl: String
Cómo actualizar MarsApiService y OverviewViewModel
En esta tarea, crearás un objeto de Moshi con el compilador Moshi, similar al compilador Retrofit.
Reemplaza ScalarsConverterFactory
por KotlinJsonAdapterFactory
para indicarle a Retrofit que puede usar Moshi para convertir la respuesta JSON en objetos Kotlin. Luego, actualizarás la API de la red y ViewModel
para usar el objeto Moshi.
- Abre
network/MarsApiService.kt
. Observa los errores de referencia no resueltos paraScalarsConverterFactory
. Esto se debe al cambio de dependencia de Retrofit que realizaste en un paso anterior. Borra la importación deScalarConverterFactory
. Pronto corregirás el otro error.
Quitar:
import retrofit2.converter.scalars.ScalarsConverterFactory
- En la parte superior del archivo, justo antes de la creación de Retrofit, agrega el siguiente código para crear el objeto Moshi similar al objeto Retrofit.
private val moshi = Moshi.Builder()
Importa com.squareup.moshi.Moshi
y com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
cuando se te solicite.
- Para que las anotaciones de Moshi funcionen correctamente con Kotlin, en el compilador de Moshi, agrega
KotlinJsonAdapterFactory
y, luego, llama abuild()
.
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
- En la declaración del objeto
retrofit
, cambia el creador de Retrofit para usarMoshiConverterFactory
en lugar deScalarConverterFactory
y pasa la instancia demoshi
que acabas de crear.
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
Importa retrofit2.converter.moshi.MoshiConverterFactory
cuando se solicite.
- Ahora que
MoshiConverterFactory
está implementado, puedes pedirle a Retrofit que muestre una lista de objetosMarsPhoto
del array JSON en lugar de mostrar una string JSON. Actualiza la interfazMarsApiService
para que Retrofit muestre una lista de objetosMarsPhoto
, en lugar de mostrarString
.
interface MarsApiService {
@GET("photo")
fun getPhotos(): List<MarsPhoto>
}
- Haz cambios similares a
viewModel
y abreOverviewViewModel.kt
. Desplázate hacia abajo hasta el métodogetMarsPhotos()
. - En el método
getMarsPhotos()
,listResult
esList<MarsPhoto>
, y ya no es unString
. El tamaño de la lista es la cantidad de fotos que se recibieron y analizaron. Para imprimir la cantidad de fotos recuperadas, actualiza_status.
value
de la siguiente manera:
_status.value = "Success: ${listResult.size} Mars photos retrieved"
Importa com.example.android.marsphotos.network.MarsPhoto
cuando se te solicite.
- Asegúrate de que el modo de avión esté desactivado en el dispositivo o emulador. Compila y ejecuta la app. Esta vez, el mensaje debería mostrar la cantidad de propiedades que se muestran del servicio web, no una string grande en JSON:
build.gradle(Module : MarsPhotos.app)
Estas son las dependencias nuevas que se incluirán.
dependencies { ... // Moshi implementation 'com.squareup.moshi:moshi-kotlin:1.9.3' // Retrofit with Moshi Converter implementation 'com.squareup.retrofit2:converter-moshi:2.9.0' ... }
Manifests/AndroidManifest.xml
Agrega el permiso de Internet, código <uses-permission..>
, desde el fragmento de código siguiente.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.marsphotos">
<!-- In order for our app to access the Internet, we need to define this permission. -->
<uses-permission android:name="android.permission.INTERNET" />
<application
...
</application>
</manifest>
network/MarsPhoto.kt
package com.example.android.marsphotos.network
import com.squareup.moshi.Json
/**
* This data class defines a Mars photo which includes an ID, and the image URL.
* The property names of this data class are used by Moshi to match the names of values in JSON.
*/
data class MarsPhoto(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String
)
network/MarsApiService.kt
package com.example.android.marsphotos.network
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
private const val BASE_URL =
"https://android-kotlin-fun-mars-server.appspot.com"
/**
* Build the Moshi object with Kotlin adapter factory that Retrofit will be using.
*/
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
/**
* The Retrofit object with the Moshi converter.
*/
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
/**
* A public interface that exposes the [getPhotos] method
*/
interface MarsApiService {
/**
* Returns a [List] of [MarsPhoto] and this method can be called from a Coroutine.
* The @GET annotation indicates that the "photos" endpoint will be requested with the GET
* HTTP method
*/
@GET("photos")
suspend fun getPhotos() : List<MarsPhoto>
}
/**
* A public Api object that exposes the lazy-initialized Retrofit service
*/
object MarsApi {
val retrofitService: MarsApiService by lazy { retrofit.create(MarsApiService::class.java) }
}
Overview/OverviewViewModel.kt
package com.example.android.marsphotos.overview
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.android.marsphotos.network.MarsApi
import kotlinx.coroutines.launch
/**
* The [ViewModel] that is attached to the [OverviewFragment].
*/
class OverviewViewModel : ViewModel() {
// The internal MutableLiveData that stores the status of the most recent request
private val _status = MutableLiveData<String>()
// The external immutable LiveData for the request status
val status: LiveData<String> = _status
/**
* Call getMarsPhotos() on init so we can display status immediately.
*/
init {
getMarsPhotos()
}
/**
* Gets Mars photos information from the Mars API Retrofit service and updates the
* [MarsPhoto] [List] [LiveData].
*/
private fun getMarsPhotos() {
viewModelScope.launch {
try {
val listResult = MarsApi.retrofitService.getPhotos()
_status.value = "Success: ${listResult.size} Mars photos retrieved"
} catch (e: Exception) {
_status.value = "Failure: ${e.message}"
}
}
}
}
Servicios web de REST
- Un servicio web es una funcionalidad basada en software que se ofrece a través de Internet y que permite que tu app haga solicitudes y recupere datos.
- Los servicios web comunes usan una arquitectura REST. Los servicios web que ofrecen arquitectura REST se conocen como servicios RESTful. Los servicios web RESTful se crean con componentes y protocolos web estándar.
- Realizas una solicitud a un servicio web REST de manera estandarizada mediante URI.
- Para usar un servicio web, una app debe establecer una conexión de red y comunicarse con el servicio. Luego, la app debe recibir y analizar los datos de respuesta a un formato que pueda usar la app.
- La biblioteca Retrofit es una biblioteca cliente que permite a tu aplicación realizar solicitudes a un servicio web REST.
- Usar conversores para indicarle a Retrofit qué hacer con los datos que envía al servicio web y regresa del servicio web. Por ejemplo, el conversor de
ScalarsConverter
trata los datos del servicio web comoString
o cualquier otra primitiva. - Para permitir que tu app establezca conexiones a Internet, agrega el permiso
"android.permission.INTERNET"
en el manifiesto de Android.
Análisis de JSON
- A menudo, la respuesta de un servicio web tiene el formato JSON, un formato común para representar datos estructurados.
- Un objeto JSON es una colección de pares clave-valor.
- Una colección de objetos JSON es un array JSON. Obtienes un array JSON como una respuesta de un servicio web.
- Las claves en un par clave-valor están entre comillas. Los valores pueden ser números o strings.
- La biblioteca Moshi es un analizador de JSON de Android que convierte una string JSON en objetos Kotlin. Retrofit tiene un conversor que funciona con Moshi.
- Moshi hace coincidir las claves en una respuesta JSON con propiedades en un objeto de datos que tienen el mismo nombre.
- A fin de usar un nombre de propiedad diferente para una clave, anota esa propiedad con la anotación
@Json
y el nombre de la clave JSON.
Documentación para desarrolladores de Android:
Documentación de Kotlin:
- Excepciones: probar, capturar, finalmente, arrojar, nada
- Codelab de corrutinas
- Corrutinas, documentación oficial
- Contexto de corrutinas y despachadores
Otro: