Inicio > Android, Código, Delphi, iOS, JSON > Hablando del tiempo… (OpenWeatherMap) 2/2

Hablando del tiempo… (OpenWeatherMap) 2/2

Lunes, 21 de Diciembre de 2015 Dejar un comentario Ir a comentarios
Share Button

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.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 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:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 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;

prox_horasAl 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.

droid@screen-1 droid@screen-4

 droid@screen-3 droid@screen-2

ios4  iOS3

iOS2  iOS1

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).

<CODIGO_FUENTE_PROYECTO>

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

GooglePlay

Hasta la próxima.

Share Button
Categories: Android, Código, Delphi, iOS, JSON Tags: , , ,
  1. Fredy Caballero
    Martes, 22 de Diciembre de 2015 a las 00:12 | #1

    Muchas gracias por compartir, como siempre información y consejos muy útiles.

  2. Tatty Fuentes
    Sábado, 14 de Enero de 2017 a las 23:15 | #2

    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!.

  1. Sin trackbacks aún.
What is 14 + 6 ?
Please leave these two fields as-is:
IMPORTANTE! Para continuar, debes contestar la pregunta anterior (para evitar SPAM) :-)