Acceso a información meteorológica con GSON y Picasso

Acceso a información meteorológica con GSON y Picasso

Tratar con las conexiones de red es un punto crucial en el desarrollo de Android. La descarga y el análisis de datos del formato original es una tarea común para las aplicaciones conectadas a la red y generalmente consta de dos pasos. El primer paso es conectar la aplicación a datos de origen remotos. Esta operación debe realizarse en un subproceso separado para evitar cualquier retraso en la comunicación que pueda causar una experiencia de usuario débil. El siguiente paso es leer los datos de la fuente, generalmente mediante Java Streams, y convertirlos del formato original a objetos Java. En este tutorial, aprenderá a descargar y analizar datos de OpenWeatherMap.org, un servicio en línea que almacena información sobre el clima mundial y la ofrece a los desarrolladores como un servicio web en formato JSON, XML y HTML.

El siguiente ejemplo mostrará cómo interactuar con este servicio aprovechando dos impresionantes bibliotecas de Java: GSON (para el análisis de JSON) y Picasso (para la descarga eficiente de imágenes).

De los datos al diseño

Para descargar datos sobre el tiempo actual, debe conectarse a OpenWeatherMap.org mediante la dirección http : http://api.openweathermap.org/data/2.5/weather . Al contactar la URL, se concatenará con una cadena de consulta que contiene un parámetro que indica el nombre de la ciudad que le interesa.
Por ejemplo, si quisiera saber el clima actual en Londres, enviaría una solicitud GET al dirección http://api.openweathermap.org/data/2.5/weather?q=London .

En el ejemplo, descargaremos datos sobre algunas ciudades importantes. Realizaremos una solicitud HTTP diferente para cada ciudad y todas las solicitudes tendrán el formato mencionado anteriormente.

La información recopilada se mostrará en un ListView como puede ver en la siguiente imagen:

captura de pantalla

Toda la información (temperatura, ícono a la izquierda y descripción del clima) se proporciona desde OpenWeatherMap.org en un formato JSON como este:

{
   "coord":{
      "lon":-0.13,
      "lat": 51,51
   },
   "sistema":{
      "tipo 1,
      "id":5091,
      "mensaje":0.0192,
      "país":"GB",
      "amanecer": 1425623634,
      "puesta de sol": 1425664170
   },
   "clima":[
      {
         "identificación": 800,
         "principal":"Borrar",
         "description":"El cielo está despejado",
         "icono":"01d"
      }
   ],
   "base":"estaciones cmc",
   "principal":{
      "temp":283.2,
      "presión": 1030,
      "humedad": 57,
      "temp_min":280.93,
      "temp_max":285.37
   },
   "viento":{
      "velocidad": 5.7,
      "grados":210
   },
   "nubes":{
      "todos":0
   },
   "dt":1425664129,
   "id":2643743,
   "nombre":"Londres",
   "bacalao":200
}

Las bibliotecas: GSON y Picasso

GSON es una biblioteca de Java que admite la conversión de objetos JSON a Java y viceversa. Los desarrolladores pueden beneficiarse de GSON cuando tienen que convertir un archivo JSON con una estructura compleja. GSON puede hacerlo automáticamente usando la reflexión de Java. Puede manejar tipos de datos simples, objetos y colecciones comunes de Java.
Picasso es una biblioteca creada y mantenida por Square Open Source ( http://square.github.io/ ). Es útil para obtener imágenes de Internet, almacenarlas en caché para optimizar el uso de la red y vincularlas a un widget de ImageView.
Ambas bibliotecas se pueden incluir en proyectos de Android usando Maven o descargándolas como un archivo JAR desde los siguientes enlaces:
Picasso: http://square.github.io/picasso/#download
GSON:http://code.google.com/p/google-gson/

El código

En primer lugar, la estructura de cada fila en la interfaz de usuario está diseñada con el siguiente diseño:

fila.xml

<?versión xml="1.0" codificación="utf-8"?>
<RelativeLayout xmlns_android="http://schemas.android.com/apk/res/android"
    android_layout_width="match_parent"
    android_layout_height="wrap_content"
    android_fondo="#CCFFDD"
    android_relleno="5dp" >

    <ImagenVista
        Android_id="@+id/img"
        android_layout_width="wrap_content"
        android_layout_height="wrap_content"
        android_layout_centerVertical="verdadero" />

    <Vista de texto
        android_id="@+id/ciudad"
        android_layout_width="wrap_content"
        android_layout_height="wrap_content"
        android_layout_alignTop="@id/img"
        android_layout_marginLeft="10dp"
        android_layout_toRightOf="@+id/img"
        android_textSize="20sp"
        android_textStyle="negrita|cursiva" />

    <Vista de texto
        android_id="@+id/descripción"
        android_layout_width="wrap_content"
        android_layout_height="wrap_content"
        android_layout_alignLeft="@id/ciudad"
        android_layout_below="@id/ciudad"
        android_textSize="18sp" />

    <Vista de texto
        android_id="@+id/temperatura"
        android_layout_width="wrap_content"
        android_layout_height="wrap_content"
        android_layout_alignParentRight="verdadero"
        android_layout_centerVertical="verdadero"
        android_textSize="25sp" />

</RelativeLayout>

Producción :

fila

Corresponde al archivo /res/layout/row.xml.

El siguiente código es la clase MainActivity:

MainActivity.java

paquete com.androidbegin.weather;

importar java.io.IOException;
importar java.io.InputStreamReader;
importar java.io.Reader;
importar java.net.MalformedURLException;
importar java.net.URL;

importar com.google.gson.Gson;

importar android.app.ListActivity;
importar android.os.AsyncTask;
importar android.os.Bundle;

clase pública MainActivity extiende ListActivity {
	// Dirección base para descarga de información
	Cadena estática final privada BASE_ADDR = "http://api.openweathermap.org/data/2.5/weather";

	// La siguiente matriz contiene los nombres de las ciudades que nos interesan
	Private String[] ciudades = new String[] { "Tokio", "Londres", "Moscú",
			"Ottawa", "Madrid", "Lisboa", "Zúrich" };

	// Referencia al objeto Adaptador. WeatherAdapter es una clase personalizada,
	// definido en un archivo separado

	adaptador WeatherAdapter privado;

	/*
	 * Mezclar el objeto String que contiene el nombre de la ciudad con la base
	 * dirección, podemos generar la dirección HTTP completa para contactar. su formato
	 * debería ser así:
	 * http://api.openweathermap.org/data/2.5/weather?q=London Este es el
	 * Propósito del método getDataAddress.
	 */
	cadena privada getDataAddress(String ciudad) {
		return BASE_ADDR + "?q=" + ciudad;
	}

	@Anular
	Vacío protegido onCreate (Paquete de estado de instancia guardado) {
		super.onCreate(estadoDeInstanciaGuardado);

		// El objeto adaptador es instanciado
		adaptador = new WeatherAdapter(esto);

		// El adaptador está vinculado a ListView
		setListAdapter(adaptador);

		// Solicitamos información por separado vía HTTP para cada ciudad en el
		// matriz
		para (Cadena c : ciudades)
			cargarJson(c);
	}

	// Este método realiza la solicitud remota y usa GSON para analizar datos JSON
	protegido vacío loadJson (Cadena seleccionada) {

		// Ampliamos la clase AsyncTask e instanciamos un objeto directamente.
		new AsyncTask<Cadena, Vacío, Datos>() {

			// El método doInBackground contiene las operaciones más lentas que queremos
			// para actuar en un hilo separado
			@Anular
			datos protegidos doInBackground(String... params) {
				// La matriz Params contiene solo un objeto y su valor es el
				// cadena que representa el nombre de la ciudad
				String ciudadseleccionada = params[0];

				// objeto de URL que usaremos para conectarnos al host remoto
				URL URL;
				intentar {
					// Se crea el objeto URL. El parámetro de entrada que pasamos es el
					// URL de contacto
					url = nueva URL(getDataAddress(selectedCity));

					// Después de la conexión, la URL proporciona la transmisión al control remoto
					// datos. El objeto lector se puede usar para leerlos.
					Reader dataInput = new InputStreamReader(url.openStream());

					// GSON necesita un flujo para leer datos. Pasamos dos parámetros
					// al método fromJson: el flujo a leer y la clase de datos
					// estructura para análisis automático
					Datos datos = new Gson().fromJson(dataInput, Data.class);
					devolver datos;
				} captura (Excepción URL mal formada e1) {
					devolver nulo;
				} captura (IOException e1) {
					devolver nulo;
				}
			}

			// El método onPostExecute recibe el valor de retorno de
			// hacerEnFondo. Recuerda que onPostExecute funciona en el principal
			// hilo de la aplicación
			vacío protegido en PostExecute (resultado de datos) {
				si (resultado! = nulo) {
					// Si no es nulo, el objeto de datos se pasa al adaptador
					adaptador.add(resultado);
				}
			};

			// Se invoca el método de ejecución para activar el fondo
			// operación
		}.ejecutar(seleccionado);

	}

}

MainActivity es una subclase de ListActivity, por lo que ya incluye un ListView. En el método onCreate inicializaremos el adaptador, que es una clase personalizada llamada WeatherAdapter.
El método llamado loadJson realiza la interacción de red real. Invoca doInBackground de un objeto derivado de AsyncTask que abre una conexión a la dirección HTTP que proporcionará datos y enviará el flujo de entrada a GSON como un objeto Reader.
Aquí es cuando GSON descarga y convierte datos JSON en un objeto de datos. La clase de datos se define en el proyecto y sus propiedades reflejan las de JSON. GSON solo convertirá automáticamente las propiedades para las que puede encontrar una clave correspondiente en el archivo JSON:

Datos.java

paquete com.androidbegin.weather;

importar java.util.List;

// GSON convertirá los datos JSON en un objeto de datos
datos de clase {
	// Dirección base para descarga de icono
	Cadena estática final privada ICON_ADDR = "http://openweathermap.org/img/w/";

	clase estática Tiempo {
		Descripción de la cadena;
		icono de cadena;
	}

	clase estática principal {
		temperatura de flotación;
	}

	List<Tiempo> tiempo;

	Principal Principal;

	Nombre de cadena;

	// Un método que convierte la temperatura de grados Kelvin a Celsius
	Cadena getTemperatureInCelsius() {
		temperatura flotante = temperatura principal - 273.15f;
		devuelve String.format("%.2f", temp);
	}

	// getIconAddress concatena la dirección base y el código específico para
	// el icono
	cadena pública getIconAddress() {
		return ICON_ADDR + tiempo.get(0).icono + ".png";
	}

	public String getDescripción() {
		if (clima != nulo && clima.tamaño() > 0)
			return tiempo.get(0).descripción;
		devolver nulo;
	}
}

La clase WeatherAdapter amplía BaseAdapter y anula cuatro métodos: getCount, getItem, getItemId y getView.

WeatherAdapter.java

paquete com.androidbegin.weather;

importar java.util.ArrayList;

importar com.squareup.picasso.Picasso;

importar android.content.Context;
importar android.view.LayoutInflater;
importar android.view.View;
importar android.view.ViewGroup;
importar android.widget.BaseAdapter;
importar android.widget.ImageView;
importar android.widget.TextView;

clase pública WeatherAdapter extiende BaseAdapter {

	// Datos de origen: cada uno de los objetos de datos contiene información sobre una ciudad
	private ArrayList<Data> ciudades = new ArrayList<Data>();
	contexto de contexto privado;

	// Un objeto ViewHolder almacena cada una de las referencias de widgets en el diseño.
	// Evita un uso frecuente de findViewById
	clase estática privada ViewHolder {
		ImageView imgv;
		TextView ciudad;
		Descripción de Vista de texto;
		Temperatura de TextView;
	}

	WeatherAdapter público (contexto contextual) {
		este.contexto = contexto;
	}

	// Se agrega un objeto de datos a List<Data>.
	// La invocación de notificarDataSetChanged implica la actualización de ListView
	anular agregar (datos c) {
		ciudades.add(c);
		notificarDataSetChanged();
	}

	// getCount devuelve el número de elementos en la estructura de datos
	@Anular
	public int getCount() {
		return ciudades.tamaño();
	}

	// getItem devuelve el elemento almacenado en una posición específica de los datos
	// estructura
	@Anular
	Objeto público getItem(int pos) {

		return ciudades.get(pos);
	}

	/*
	 * getItemId realiza el mismo trabajo que getItem pero devuelve la identificación del objeto
	 * en lugar de la referencia al objeto
	 */
	@Anular
	getItemId largo público (int pos) {

		volver pos;
	}

	/*
	 * getView crea un objeto View que muestra información almacenada en un Data
	 * objeto en una posición específica en el ArrayList
	 */
	@Anular
	Vista pública getView(int pos, View v, ViewGroup vg) {

		ViewHolder vh = null;

		// Si v es nulo, se instancia una nueva Vista
		si (v == nulo) {
			// El LayoutInflater crea la Vista utilizando un diseño XML como modelo
			v = LayoutInflater.from(context).inflate(R.layout.row, null);

			// Las referencias a los widgets internos se almacenan en un ViewHolder
			vh = nuevo titular de vista ();
			vh.imgv = (ImageView) v.findViewById(R.id.img);
			vh.descripción = (TextView) v.findViewById(R.id.descripción);
			vh.city = (TextView) v.findViewById(R.id.city);
			vh.temperature = (TextView) v.findViewById(R.id.temperature);
			v.setTag(vh);
		} más
			// v no es nulo, por lo que no necesitamos invocar LayoutInflater
			vh = (ViewHolder) v.getTag();

		// Recuperamos el objeto en una posición específica
		Datos res = (Datos) getItem(pos);

		// La biblioteca de Picasso descarga el icono y lo establece como fuente de ImageView
		Picasso.with(contexto).load(res.getIconAddress()).into(vh.imgv);

		// Otras vistas de texto se llenan con información de objetos de datos
		vh.descripción.setText(res.getDescripción());
		vh.city.setText(res.nombre);
		vh.temperature.setText(res.getTemperatureInCelsius() + "°C");

		volver v;
	}

}

En particular, se llama al método getView cuando un objeto de datos tiene que convertirse en una vista. En el código getView se crea la estructura de la Vista inflando el diseño del archivo row.xml. Después de eso, creamos un objeto ViewHolder cuyo propósito es almacenar las referencias de las vistas en el diseño, TextViews y ImageView. Luego, el ViewHolder se vincula a la Vista que lo usará invocando el método setTag .
En el método getView, podemos ver la biblioteca de Picasso en funcionamiento. Esta herramienta ya incluye todo lo que necesitamos para obtener imágenes. La operación se realizará de forma asíncrona y Picasso se hará cargo de todas las tareas relacionadas con la descarga y el almacenamiento en caché de las imágenes.

Además, la sintaxis para la invocación de Picasso es muy sencilla y requiere una sola línea de código:

Picasso.with(contexto).load(res.getIconAddress()).into(vh.imgv);

Una vez descargado, el ícono se mostrará en ImageView al que hace referencia vh.imgv.

Finalmente, en su  AndroidManifest.xml , debemos declarar los permisos para permitir que la aplicación se conecte a Internet. Abra su  AndroidManifest.xml  y pegue el siguiente código.

Manifiesto de Android. xml

<?versión xml="1.0" codificación="utf-8"?>
<manifiesto xmlns_android="http://schemas.android.com/apk/res/android"
    paquete="com.androidbegin.weather"
    android_versionCode="1"
    android_versionName="1.0" >

    <usos-sdk
        android_minSdkVersion="14"
        android_targetSdkVersion="21" />

    <usos-permiso android_name="android.permiso.INTERNET" />

    <aplicación
        Android: permitir copia de seguridad = "verdadero"
        android_icono="@dibujable/ic_launcher"
        android_etiqueta="@cadena/nombre_aplicación"
        android_tema="@estilo/AppTheme" >
        <actividad
            android_nombre=".Actividad principal"
            android_etiqueta="@cadena/nombre_aplicación" >
            <intent-filter>
                <acción android_name="android.intent.action.MAIN" />

                <categoría android_name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </actividad>
    </aplicación>

</manifiesto>

Producción:

captura de pantalla_weatherapp

Código fuente

[purchase_link id=”8036″ text=”Comprar para descargar el código fuente” style=”button” color=”green”]