Hablando del tiempo… (OpenWeatherMap) 2/2
Para complementar la entrada anterior (Hablando del tiempo… (OpenWeatherMap) 1/2) y «acabarla» me queda publicar el desarrollo móvil correspondiente a la aplicación que vimos anteriormente.
Como ya os comenté en la entrada anterior, el código a utilizar es prácticamente el mismo que hemos utilizado en las aplicaciones para windows (VCL). La mayor diferencia que me he encontrado en el tratamiento de la respuesta JSON que obtenemos del servidor.
Para versiones antiguas de Delphi, podéis utilizar si lo necesitáis la misma librería que ya he recomendado aquí otras veces. Se trata de lkJSON que podéis encontrar en Sourceforge.
En las nuevas versiones de Delphi ya está disponible la unit System.JSON, con lo necesario para no utilizar librerías externas.
function TForm1.ParseTiempoCiudadesHist(AObjResp: TlkJSONobject; var ATiempoProxHoras: TTiempoProxHoras): Boolean; var i, j, num, index:integer; oHorasList, OWList:TlkJSONlist; oHora, oCoord, oMain, oWind, oWeather:TlkJSONobject; Str:String; begin // ini Result := False; // Si no está asignado salimos.. if not Assigned(AobjResp) then begin Exit; end; // Si hay error no parseamos if IsErrorResponse(AObjResp, errCode, ErrMsg) then begin Exit; end; // proteccion para el parseo try // cod. devuelto (datos principales ATiempoProxHoras.Cod := errCode; num := AObjResp.IndexOfName('count'); if (num <> -1) then begin num := GetAsInteger(AObjResp.Field['count'].Value); end; // si no hay ciudades if (num = 0) then begin MessageDlg('No hay ninguna ciudad que coincida con ese código [nombre,pais].', mtWarning, [mbOK], 0); Exit; end; // Lista de horas (Lista) TlkJSONBase(oHorasList) := AObjResp.Field['list']; // array de elementos SetLength(ATiempoProxHoras.TiempoHora, oHorasList.Count); // Quedarse con el primier elemento de la lista... for i := 0 to (oHorasList.Count - 1) do begin // datos de la primera ciudad TlkJSONBase(oHora) := oHorasList.Child[i]; // datos básicos ATiempoProxHoras.TiempoHora[i].dt_text := GetAsString(oHora.Field['dt_txt'].Value); // convertir fecha-Hora Str := ATiempoProxHoras.TiempoHora[i].dt_text; ATiempoProxHoras.TiempoHora[i].dt := EncodeDateTime( StrToIntdef(Copy(Str, 1, 4), 0), StrToIntdef(Copy(Str, 6, 2), 0), StrToIntdef(Copy(Str, 9, 2), 0), StrToIntdef(Copy(Str, 12, 2), 0), StrToIntdef(Copy(Str, 15, 2), 0), StrToIntdef(Copy(Str, 18, 2), 0), 0); // Load Main TlkJSONBase(oMain) := oHora.Field['main']; ATiempoProxHoras.TiempoHora[i].Main.temp := GetAsFloat(oMain.Field['temp'].Value); ATiempoProxHoras.TiempoHora[i].Main.tempmin := GetAsFloat(oMain.Field['temp_min'].Value); ATiempoProxHoras.TiempoHora[i].Main.tempmax := GetAsFloat(oMain.Field['temp_max'].Value); ATiempoProxHoras.TiempoHora[i].Main.pressure := GetAsFloat(oMain.Field['pressure'].Value); ATiempoProxHoras.TiempoHora[i].Main.humidity := GetAsInteger(oMain.Field['humidity'].Value); // Load weather TlkJSONBase(OWList) := oHora.Field['weather']; TlkJSONBase(oWeather) := oWList.Child[0]; ATiempoProxHoras.TiempoHora[i].Weather.id := GetAsInteger(oWeather.Field['id'].Value); ATiempoProxHoras.TiempoHora[i].Weather.main := GetAsString(oWeather.Field['main'].Value); ATiempoProxHoras.TiempoHora[i].Weather.desc := GetAsString(oWeather.Field['description'].Value); ATiempoProxHoras.TiempoHora[i].Weather.icon := GetAsString(oWeather.Field['icon'].Value); end; Result := True; except // si hay error, FALSe Result := False; end; end; |
Posíblemente las mayores diferencias (que en realidad no son tantas) se encuentran en la parte en la que accedemos a la lista de elementos que nos devuelve el WebService.
La estructura que utilizamos para almacenar estos datos, la podéis ver en el código del proyecto (todas estas definiciones están englobadas en la unit UTWeatherClass) y es la siguiente:
// estructura para varias horas TTiempoProxHoras = record Cod:Integer; // cod. tiempo City:string; Country:string; TiempoHora:array of TTiempoProxHora; // Array para cada hora End; // datos para una hora TTiempoProxHora = record dt:TDateTime; dt_text:string; main:TMain; weather: TWeather; // datos de tiempo end; // Main para tiempo TMain = record temp:double; // temperatura humidity:double; // humedad % pressure:double; tempmin:double; tempmax:double; sealevel:Double; // atm presure grndlevel:Double; end; // Tiempo TWeather = record id:integer; main: string; desc: string; icon: string; end; |
Hago un inciso.
Esta unit, es un buen ejemplo de cómo compartir código entre aplicaciones que van a ser desarrolladas para diferentes plataformas.
Delphi permite desarrollar aplicaciones multiplataforma. Eso no significa que la misma aplicación la podamos compilar y funcione en Windows, Android, iOS y OSX. En teoría si, pero en la prácira salvo alguna excepción contada, tendremos que desarrollar cosas distintas. Sin ir más lejos, es difícil que un interface para Android nos pueda ser útil para una aplicación Windows.
Eso se traduce en que al final (salvo contadas excepciones, como he dicho) tendremos que diseñar un interface «personalizado» para cada plataforma, y reaprovechar todo el código que podamos, siendo responsables en el diseño de nuestra aplicación.
No es algo nuevo de ahora, desde hace tiempo, un buen diseño aboga por la separación entre la interficie y la «lógica de negocio»; Pues esto da valor a esa afirmación.
Volviendo al tema del tiempo, nuestra implementación utilizando la librería System.JSON, será la siguiente:
// Parsea varias horas de tiempo para una ciudad function TFormMain.ParseTiempoCiudadesHist(AObjResp: TJSONobject; var ATiempoProxHoras: TTiempoProxHoras): Boolean; var i, j, num, index:integer; oCityList, oList, oArr:TJSONArray; oDatosHora, oCoord, oMain, oWind, oWeather:TJSONobject; Str:String; oPair, oCity:TJSONPair; oItem:TJSONValue; begin // ini Result := False; // Si no está asignado salimos.. if not Assigned(AobjResp) then begin Exit; end; // Si hay error no parseamos if IsErrorResponse(AObjResp, errCode, ErrMsg) then begin Exit; end; // proteccion para el parseo try // cod. devuelto (datos principales ATiempoProxHoras.Cod := errCode; // buscar la ciudad oCity := TJSONPair(AObjResp.Get('city')); ATiempoProxHoras.City := GetJSONPairItemString(oCity, 'name'); ATiempoProxHoras.Country := GetJSONPairItemString(oCity, 'country'); // Parsear lal lista de horas oArr := TJSONArray(AObjResp.Get('list').JsonValue); num := oArr.size; // Si no ha encontrado ninguna, salimos... if (num = 0) then begin MessageDlg('No hay ninguna ciudad que coincida con ese código [nombre,pais].', TMsgDlgType.mtWarning, [TMsgDlgBtn.mbOK], 0); Exit; end; // Tamaño de array que devolvemos Setlength(ATiempoProxHoras.TiempoHora, num); // Recorrido por las diferentes horas for i := 0 to (num - 1) do begin oDatosHora := TJSONObject(oArr.Get(i)); // fecha/Hora ATiempoProxHoras.TiempoHora[i].dt_text := GetAsString(oDatosHora.GetValue('dt_txt').Value); Str := ATiempoProxHoras.TiempoHora[i].dt_text; // convertir fecha-Hora ATiempoProxHoras.TiempoHora[i].dt := EncodeDateTime( StrToIntdef(Copy(Str, 1, 4), 0), StrToIntdef(Copy(Str, 6, 2), 0), StrToIntdef(Copy(Str, 9, 2), 0), StrToIntdef(Copy(Str, 12, 2), 0), StrToIntdef(Copy(Str, 15, 2), 0), StrToIntdef(Copy(Str, 18, 2), 0), 0); // Parseo de Main oPair := TJSONPair(oDatosHora.Get('main')); // Los de main ATiempoProxHoras.TiempoHora[i].main.temp := GetJSONPairItemFloat(oPair, 'temp'); // Los de main ATiempoProxHoras.TiempoHora[i].Main.temp := GetJSONPairItemFloat(oPair, 'temp'); ATiempoProxHoras.TiempoHora[i].Main.tempmin := GetJSONPairItemFloat(oPair, 'temp_min'); ATiempoProxHoras.TiempoHora[i].Main.tempmax := GetJSONPairItemFloat(oPair, 'temp_max'); ATiempoProxHoras.TiempoHora[i].Main.pressure := GetJSONPairItemFloat(oPair, 'pressure'); ATiempoProxHoras.TiempoHora[i].Main.sealevel := GetJSONPairItemFloat(oPair, 'sea_level'); ATiempoProxHoras.TiempoHora[i].Main.grndlevel := GetJSONPairItemFloat(oPair, 'grnd_level'); ATiempoProxHoras.TiempoHora[i].Main.humidity := GetJSONPairItemFloat(oPair, 'humidity'); // weather oList := TJSONArray(oDatosHora.Get('weather').JsonValue); oWeather := TJSONObject(oList.Get(0)); ATiempoProxHoras.TiempoHora[i].weather.id := StrToInt(oWeather.GetValue('id').Value); ATiempoProxHoras.TiempoHora[i].weather.main := oWeather.GetValue('main').Value; ATiempoProxHoras.TiempoHora[i].weather.desc := oWeather.GetValue('description').Value; ATiempoProxHoras.TiempoHora[i].weather.icon := oWeather.GetValue('icon').Value; end; Result := True; except // si hay error, FALSe Result := False; end; end; |
Al igual que este, podeís comparar el resto de procedimientos que recuperan datos del WebService y los transforman y veréis que son bastante similares y fáciles de adaptar.
Otra cosa que podéis ver funcionando en el ejemplo son los Frames.
Hasta ahora no los había utilizado en desarrollo móvil y decidí realizar la prueba con este ejemplo, ya que se ajustaban perfectamente a la necesidad de la visualización de datos atmotféricos en la próximas horas.
Volvemos a la misma idea de antes. La creación del frame es diferentes en ambos proyectos (sólo de la parte visual), en este caso obligado puesto que uno está diseñado con la VCL y el otro con Firemonkey, pero a partir de ahí, la utilización dentro del proyecto y el código utilizado es casi idéntico.
El funcionamiento ha sido el esperado y en todas las plataformas se ha comportado correctamente.
Os dejo algunas imágenes de cómo se ve la aplicación funcionando en las diferentes plataformas.
Y un pequeño vídeo de la compilación y ejecución en los diferentes sistemas.
Os dejo también el código fuente del proyecto, que se añade a los de la anterior entrada (Hablando del tiempo… (OpenWeatherMap) 1/2).
Actualización (21/12/2015): Estoy subiendo la aplicación a la tienda de Google, para que podáis probarla desde allí. En cuanto esté disponible os adjunto el enlace.
Actualización (21/12/2015 18:30): Ya está activa la aplicación y podeís descargarla desde el siguiente enlace:
https://play.google.com/store/apps/details?id=com.embarcadero.ComoEstaElTiempo
Hasta la próxima.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Muchas gracias por compartir, como siempre información y consejos muy útiles.
hola German, te escribo en este post porque no me has respondido en el anterior… ehhh….
quiero contarte he buscado y he hecho ejercicios para parsear archivos json de arrays y ha sido en vano y muy poca la ayuda, es por eso que recurro a esta fuente q me dejaste, pero aun sigo en las mismas, te cuento que este es el codigo que he hecho y no estoy segura de lo q esta mal…:
procedure TForm1.parseo;
var
JSON: TlkJSONbase;
UnArrayJSON, OtroArrayJSON: TlkJSONbase;
n, i, v1, var1: Integer;
memo: string;
v2,v3,v4,v5,v6,v7,v8,
var2,var3,var4: Variant;
clientDataSet_1, clientDataSet_2: TClientDataSet;
begin
respuesta;
memo:= mmoResp.Text;
JSON:= TlkJSONcustomlist.Create;
clientDataSet_1:= cd1;
clientDataSet_2:= cd2;
try
//parseamos el json
JSON:= TlkJSON.ParseText(memo) as TlkJSONcustomlist;
//obtenemos los objetos json principales
UnArrayJSON:= JSON.Field[‘Items’] as TlkJSONcustomlist;
OtroArrayJSON:= JSON.Field[‘procedimientos’] as TlkJSONcustomlist;
//recorremos el array y validamos el campo
for n:=0 to UnArrayJSON.Count -1 do
if UnArrayJSON.Child[n].Field[‘id_muestra’].value = 297 then begin
v1:= UnArrayJSON.Child[0].Field[‘id_muestra’].Value;
v2:= UnArrayJSON.Child[1].Field[‘paciente_documento’].Value;
v3:= UnArrayJSON.Child[2].Field[‘paciente_nombre’].Value;
v4:= UnArrayJSON.Child[3].Field[‘fecha_nacimiento’].Value;
v5:= UnArrayJSON.Child[4].Field[‘numero_muestra’].Value;
v6:= UnArrayJSON.Child[5].Field[‘sexo’].Value;
v7:= UnArrayJSON.Child[6].Field[‘tipo_id’].Value;
v8:= UnArrayJSON.Child[7].Field[‘nit_empresa’].Value;
//añadimos registroa al cds
clientDataSet_1.EmptyDataSet;
try
clientDataSet_1.Append;
clientDataSet_1.FieldByName(‘id_muestra’).AsInteger := v1;
clientDataSet_1.FieldByName(‘paciente_documento’).AsVariant := (v2);
clientDataSet_1.FieldByName(‘paciente_nombre’).AsVariant := (v3);
clientDataSet_1.FieldByName(‘fecha_nacimiento’).AsVariant := (v4);
clientDataSet_1.FieldByName(‘numero_muestra’).AsVariant := (v5);
clientDataSet_1.FieldByName(‘sexo’).AsVariant := (v6);
clientDataSet_1.FieldByName(‘tipo_id’).AsVariant := (v7);
clientDataSet_1.FieldByName(‘nit_empresa’).AsVariant := (v8);
clientDataSet_1.Post;
finally
clientDataSet_1.Cancel;
end;
//recorremos el array de procedimientos
for i:=0 to OtroArrayJSON.Count -1 do
if OtroArrayJSON.Child[i].Field[‘id’].Value = 257 then begin
var1:= OtroArrayJSON.Child[0].Field[‘id’].Value;
var2:= OtroArrayJSON.Child[1].Field[‘procedimiento_codigo’].Value;
var3:= OtroArrayJSON.Child[2].Field[‘codigo_cups’].Value;
var4:= OtroArrayJSON.Child[3].Field[‘procedimiento_nombre’].Value;
//añadimos registros al cds
clientDataSet_2.EmptyDataSet;
try
clientDataSet_2.Append;
clientDataSet_2.FieldByName(‘id’).AsInteger := var1;
clientDataSet_2.FieldByName(‘procedimiento_codigo’).AsVariant := (var2);
clientDataSet_2.FieldByName(‘codigo_cups’).AsVariant := (var3);
clientDataSet_2.FieldByName(‘procedimiento_nombre’).AsVariant := (var4);
clientDataSet_2.Post
finally
clientDataSet_2.Cancel;
end;
end;
end else begin
MessageDlg(‘error en el ciclo’, mtError,mbOKCancel,6);
end;
finally
JSON.Free;
UnArrayJSON.Free;
OtroArrayJSON.Free;
end;
end;
y en esta linea me arroja una excepcion
(v3:= UnArrayJSON.Child[2].Field[‘paciente_nombre’].Value;)
creo que porque el campo ‘paciente_nombre’ del json viene null, necesito de verdad ayuda!.