ExamenInformando al mundo
Todo sobre el Covid-19
Proyectos de desarrollo para dispositivos móvilesProfesor: Roberto Martínez Román
Ing. Roberto Martínez Román - [email protected]
Problema• Crear una app para Android que permita consultar
el estado actual de cada país con respecto a la pandemia de Covid-19.
Ing. Roberto Martínez Román - [email protected]
Crear el proyecto
Ing. Roberto Martínez Román - [email protected]
RecyclerView
• Agrega un componente RecyclerView a la pantalla principal. Espera a que se sincronice el proyecto.
Ing. Roberto Martínez Román - [email protected]
Renglones del RecyclerView• Primero, diseñamos la vista para cada renglón.
Ing. Roberto Martínez Román - [email protected]
Diseñando el renglón
• Agrega un Layout Resource File al proyecto.
Ing. Roberto Martínez Román - [email protected]
Diseño del renglón• Agrega componentes como se ve a continuación.
1/4 3/4
Ambos LinearLayout:imgBandera
Ing. Roberto Martínez Román - [email protected]
Clase Pais• Esta clase sirve para crear objetos que representan
un país. Por ahora solo tendrá dos variables de instancia:• Nombre• Casos
Pais- nombre: String- casos: Int
<Interface>Comparable<T>
+ compareTo(otro:Pais) : Int
Ing. Roberto Martínez Román - [email protected]
Adaptador del RecyclerView• El componente RecyclerView toma los valores para
sus renglones de un adaptador.
class AdaptadorPais (private val contexto: Context, var arrPaises: Array<Pais>) : RecyclerView.Adapter<AdaptadorPais.RenglonMateria>(){}
Ing. Roberto Martínez Román - [email protected]
Adaptador
class AdaptadorPais (private val contexto: Context, var arrPaises: Array<Pais>) : RecyclerView.Adapter<AdaptadorPais.RenglonMateria>(){
}
Adaptador
2 variables de instancia
Constructor Clase Pais
AdaptadorPais- contexto: Context- arrPaises: Array<Pais>
Representa la vistadel renglón
Llama al constructorde la superclase
Ing. Roberto Martínez Román - [email protected]
• Crea la clase RenglonMateria
La clase RenglonMateria
inner class RenglonMateria (var vistaRenglon: View) : RecyclerView.ViewHolder(vistaRenglon){
}
Hereda de
Variable de instancia y Constructor
Ing. Roberto Martínez Román - [email protected]
Métodos del adaptador
Implementa los 3 métodos del adaptador.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RenglonMateria { // Cuando se crea un renglón }
override fun getItemCount(): Int { // Regresa el número de renglones }
override fun onBindViewHolder(holder: RenglonMateria, position: Int) { // Cuando se llena de valores el renglón 'position' }
Ing. Roberto Martínez Román - [email protected]
Métodosoverride fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RenglonMateria { val vista = LayoutInflater.from(contexto).inflate(R.layout.renglon_pais, parent, false) return RenglonMateria(vista) }
override fun getItemCount(): Int { return arrPaises.size }
override fun onBindViewHolder(holder: RenglonMateria, position: Int) { val pais = arrPaises[position] holder.vistaRenglon.tvPais.text = pais.nombre holder.vistaRenglon.tvCasos.text = "${pais.casos}" holder.vistaRenglon.imgBandera.setImageResource(R.drawable.flag) }
Agrega un archivo en res/drawable
Ing. Roberto Martínez Román - [email protected]
Asignando el adaptador• Primero, declara la variable de instancia.var adaptadorPais: AdaptadorPais? = null
• En el método onCreate, creamos el adaptador y lo asignamos al RecyclerView.
var layout = LinearLayoutManager(this) layout.orientation = LinearLayoutManager.VERTICAL recyclerPaises.layoutManager = layout
adaptadorPais = AdaptadorPais(this, Pais.arrPaises) recyclerPaises.adapter = adaptadorPais
Ing. Roberto Martínez Román - [email protected]
Corriendo la app
Ing. Roberto Martínez Román - [email protected]
Algunos ajustes• Al hacer click sobre un renglón no hay
retroalimentación.android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true"
• No se muestra una línea entre renglones.val divisor = DividerItemDecoration(this, layout.orientation) recyclerPaises.addItemDecoration(divisor)
Ing. Roberto Martínez Román - [email protected]
Networking• Vamos a usar un framework para simplificar la
descarga de información.
• https://amitshekhar.me/Fast-Android-Networking/
Ing. Roberto Martínez Román - [email protected]
Agrega la librería
Agrega la librería en el archivo build.gradle y sincroniza.implementation 'com.amitshekhar.android:android-networking:1.0.2'
Agrega el permiso de Internet en el manifiesto.<uses-permission android:name="android.permission.INTERNET" />
En el método onCreate de la clase principal inicializa la librería.AndroidNetworking.initialize(getApplicationContext());
Ing. Roberto Martínez Román - [email protected]
Descargar la información• Hay un servicio web que regresa la información en
formato JSON:https://corona.lmao.ninja/countries?sort=country
[{"country":"Zimbabwe","countryInfo":{"_id":716,"iso2":"ZW","iso3":"ZWE","lat":-20,"long":30,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/zw.png"},"cases":8,"todayCases":0,"deaths":1,"todayDeaths":0,"recovered":0,"active":7,"critical":0,"casesPerOneMillion":0.5,"deathsPerOneMillion":0.07,"updated":1585713016367},{"country":"Zambia","countryInfo":{"_id":894,"iso2":"ZM","iso3":"ZMB","lat":-15,"long":30,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/zm.png"},"cases":36,"todayCases":0,"deaths":0,"todayDeaths":0,"recovered":0,"active":36,"critical":0,"casesPerOneMillion":2,"deathsPerOneMillion":null,"updated":1585713016365},
Ing. Roberto Martínez Román - [email protected]
Descargar• Escribe el método descargarInformacion y lo llamas al
final del método onCreate.private fun descargarInformacion() { val direccion = "https://corona.lmao.ninja/countries?sort=country"
AndroidNetworking.get(direccion) .build() .getAsJSONArray(object: JSONArrayRequestListener { override fun onResponse(response: JSONArray?) { // Descarga exitosa }
override fun onError(anError: ANError?) { // Error en la descarga } }) }
Inicia la descarga en
segundo plano
Listener
Ing. Roberto Martínez Román - [email protected]
La función completaprivate fun descargarInformacion() { AndroidNetworking.get("https://corona.lmao.ninja/countries?sort=country") .build() .getAsJSONArray(object: JSONArrayRequestListener { override fun onResponse(response: JSONArray?) { if (response != null ) { var arrPaises = mutableListOf<Pais>() for (i in 0 until response.length()) { val dPais = response.get(i) as JSONObject val nombre = dPais.getString("country") val casos = dPais.getInt("cases") arrPaises.add(0, Pais(nombre, casos)) } adaptadorPais?.arrPaises = arrPaises.toTypedArray() adaptadorPais?.notifyDataSetChanged() } }
override fun onError(anError: ANError?) { println(anError) Toast.makeText(baseContext, "Error en la descarga", Toast.LENGTH_LONG).show() } }) }
Ing. Roberto Martínez Román - [email protected]
Corre la app
Ing. Roberto Martínez Román - [email protected]
Información del país• Crea una actividad para mostrar la información del
país.
• Click derecho sobre el paquete, New, Activity, Empty Activity.
Ing. Roberto Martínez Román - [email protected]
Diseño de la vista128x128
Ing. Roberto Martínez Román - [email protected]
Mostrar país
• Detectar click en el RecyclerView y lanzar la segunda pantalla.
• Crea la interface ListenerRecycler
interface ListenerRecycler { fun itemClicked(position: Int) }
Ing. Roberto Martínez Román - [email protected]
Implementando la interface• Implementa la interface y sobrescribe el método.class MainActivity : AppCompatActivity(), ListenerRecycler
• Funciónoverride fun itemClicked(position: Int) { val nombrePais = adaptadorPais.arrPaises.get(position).nombre val intDatosPais = Intent(this, DatosPaisActiv::class.java) intDatosPais.putExtra("PAIS", nombrePais) startActivity(intDatosPais) }
Ing. Roberto Martínez Román - [email protected]
¿Quién genera el evento?
• En la clase AdaptadorPais, agrega la variable de instancia.
var listener: ListenerRecycler? = null
• Agrega el listener del renglón en el método onBindViewHolder.
holder.vistaRenglon.setOnClickListener { listener?.itemClicked(position) }
• Asigna al adaptador el listener.adaptadorPais?.listener = this
Ing. Roberto Martínez Román - [email protected]
Corre la app
Ing. Roberto Martínez Román - [email protected]
Muestra nombre del país• En la clase DatosPaisActiv, en el método onCreate.val nombre = intent.getStringExtra("PAIS") tvNombrePais.text = nombre
Sigue descargar la bandera y los
datos
Ing. Roberto Martínez Román - [email protected]
• Los datos que se requieren ya se descargaron desde la actividad previa. Para simplificar y practicar la descarga lo haremos con un endpoint diferente.
https://corona.lmao.ninja/countries/:nombrePais
Descarga de los datos
Final del método onCreate
{"country":"Argentina","countryInfo":{"_id":32,"iso2":"AR","iso3":"ARG","lat":-34,"long":-64,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/ar.png"},"cases":1054,"todayCases":0,"deaths":28,"todayDeaths":1,"recovered":248,"active":778,"critical":0,"casesPerOneMillion":23,"deathsPerOneMillion":0.6,"updated":1585759687308}
Ing. Roberto Martínez Román - [email protected]
Función descargarDatosPaisprivate fun descargarDatosPais(nombrePais: String?) { val direccion = "https://corona.lmao.ninja/countries/$nombrePais" AndroidNetworking.get(direccion) .build() .getAsJSONObject(object: JSONObjectRequestListener { override fun onResponse(response: JSONObject?) { val casos = response?.get("cases") tvCasosPais.text = "$casos" val recuperados = response?.get("recovered") tvRecuperadosPais.text = "$recuperados" val decesos = response?.get("deaths") tvDecesosPais.text = "$decesos" }
override fun onError(anError: ANError?) { Toast.makeText(this@DatosPaisActiv, "Error: $anError", Toast.LENGTH_LONG).show() } }) }
Ing. Roberto Martínez Román - [email protected]
Descargar la banderaprivate fun descargarDatosPais(nombrePais: String?) { val direccion = "https://corona.lmao.ninja/countries/$nombrePais" AndroidNetworking.get(direccion) .build() .getAsJSONObject(object: JSONObjectRequestListener { override fun onResponse(response: JSONObject?) { val casos = response?.get("cases") tvCasosPais.text = "$casos" val recuperados = response?.get("recovered") tvRecuperadosPais.text = "$recuperados" val decesos = response?.get("deaths") tvDecesosPais.text = "$decesos"
val dInfo = response?.get("countrInfo") as JSONObject val dirBandera = dInfo?.get("flag") as String? descargarBandera(dirBandera) }
override fun onError(anError: ANError?) { Toast.makeText(this@DatosPaisActiv, "Error: $anError", Toast.LENGTH_LONG).show() } }) }
Ing. Roberto Martínez Román - [email protected]
La función descargarBanderaprivate fun descargarBandera(dirBandera: String?) { if (dirBandera != null) { AndroidNetworking.get(dirBandera) .build() .getAsBitmap(object: BitmapRequestListener { override fun onResponse(response: Bitmap?) { imgBanderaPais.setImageBitmap(response) }
override fun onError(anError: ANError?) { Toast.makeText(this@DatosPaisActiv, "Error: $anError", Toast.LENGTH_LONG).show() } }) } }
👊👍
Ing. Roberto Martínez Román - [email protected]
Finalmente...• Para descargar los casos por día, usamos el
endpoint: https://corona.lmao.ninja/v2/historical/:country
• Para la gráfica, usamos el framework:
https://github.com/PhilJay/MPAndroidChart
Final del método onCreate
{"country":"Mexico","provinces":[],"timeline":{"cases":{..., "3/17/20":82,"3/18/20":93,"3/19/20":118,"3/20/20":164,"3/21/20":203,"3/22/20":251,"3/23/20":316,"3/24/20":367,"3/25/20":405,"3/26/20":475,"3/27/20":585,"3/28/20":717,"3/29/20":848,"3/30/20":993,"3/31/20":1094},
Ing. Roberto Martínez Román - [email protected]
descargarDatosGraficarprivate fun descargarDatosGraficar(nombrePais: String?) { val direccion = "https://corona.lmao.ninja/v2/historical/$nombrePais" AndroidNetworking.get(direccion) .build() .getAsJSONObject(object: JSONObjectRequestListener { override fun onResponse(response: JSONObject?) { val timeline = response?.get("timeline") as JSONObject? val casos = timeline?.get("cases") as JSONObject var indice = 0f for (fecha in casos.keys()) { val valor = casos.get(fecha) as Int if (valor>0) { val entrada = Entry(indice, valor+0f) entries.add(entrada) indice += 1 } } }
override fun onError(anError: ANError?) {
}
}) }
Definida en la librería para
graficar
Variable de instancia. Es un
ArrayList de Entry
Ing. Roberto Martínez Román - [email protected]
Agregar librería para graficar• En el archivo build.gradle del proyecto, agrega y
sincroniza (allprojects, repositories):maven { url 'https://jitpack.io' }
• En el archivo build.gradle de la app, agrega y sincroniza:implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
• Agrega un componente para mostrar la gráfica. No olvides los constraints.
<com.github.mikephil.charting.charts.LineChart android:id="@+id/lineChart" />
Ing. Roberto Martínez Román - [email protected]
El método crearGrafica
Llama al método después de procesar todas las fechas de datos.private fun crearGrafica() { val datos = LineDataSet(entries, "Personas") datos.setDrawValues(true) datos.lineWidth = 3f
lineChart.data = LineData(datos) lineChart.description.text = "COVID-19" lineChart.animateX(1800, Easing.EaseInOutSine) }
Ing. Roberto Martínez Román - [email protected]
Resultado final
👊
👍