Desarrollar aplicación para Android y para iOS que consume un servicio APIRest y accede a las facturas de una base de datos externa (en cualquier motor de base de datos). Desarrollamos la app iOS y Android con Delphi 12, de RAD Studio, en Firemonkey. La misma aplicación también se puede ejecutar en entornos Windows.
- Requisitos para desarrollar aplicación para iOS y Android con Delphi 12.
- Seguridad de acceso al servicio APIRest desde la aplicación Android/iOS con Delphi 12.
- Desarrollar aplicación multidispositivo para iOS y Android con acceso a base de datos externa en Delphi 12.
- Generación de APK, instalación en Android y funcionamiento de app con acceso a MySQL mediante APIRest.
- Anexo.
Requisitos para desarrollar aplicación para iOS y Android con Delphi 12
Necesitaremos disponer el IDE Delphi 12, cuya versión gratuita Delphi 12 Community Edicion podremos descargar e inistalar como indicamos en este tutorial:
Para el acceso a la base de datos externa, usaremos el servicio APIRest, por lo que deberemos disponer de este servicio desarrollado en la parte del servidor de base de datos o en la parte del servidor web que acceda a esta base de datos. En el siguiente tutorial explicamos, paso a paso y con un ejemplo completo, cómo desarrollar un APIRest en PHP:
Para este tutorial, el servicio API RESTful o APIRest reside en la URL base:
http://proyectoa.com:8080/apirestfacturas
Y el URI que usaremos para que nos devuelva un listado de facturas en JSON será:
listar.php
Con lo que la URL completa para consumir este servicio APIRest será:
http://proyectoa.com:8080/apirestfacturas/listar.php
Este servicio devuelve el siguiente JSON con el listado de facturas que haya en la tabla correspondiente del motor de base de daos al que ataca el APIRest, con estos datos de ejemplo:
|
1 |
[{"codigo":23,"cliente":"ProyectoA Cliente 6","importe":"10000.00","fecha":"2024-06-20"},{"codigo":22,"cliente":"ProyectoA Cliente 6","importe":"59988.25","fecha":"2025-01-11"},{"codigo":5,"cliente":"ProyectoA Cliente 1","importe":"750.89","fecha":"2025-01-10"},{"codigo":2,"cliente":"ProyectoA Cliente 2","importe":"500.56","fecha":"2025-01-07"},{"codigo":1,"cliente":"ProyectoA Cliente 1","importe":"1000.00","fecha":"2025-01-03"}] |
Lo importante del JSON es conocer su estructura, que en este caso está compuesto por un registro (una factura) entre llaves, que contiene, a su vez, los campos:
- codigo (número): código de la factura.
- cliente (texto): nombre del cliente de la factura.
- importe (número): importe de la factura.
- fecha (fecha): fecha de la factura.
Seguridad de acceso al servicio APIRest desde la aplicación Android/iOS con Delphi 12
No es objeto de este tutorial establecer un nivel de seguridad de acceso alto. Esto lo afrontaremos en otros tutoriales. Aunque sí conviene resaltar que en este caso, la seguridad es muy baja, dado que usaremos una conexión HTTP (texto plano sin cifrar) y, además, el acceso al APIRest no incluye mecanismos de autorización (como tokens u otros).
Lo recomendable es usar siempre protocolo HTTPS (SSL) para que el tráfico entre el dispositivo Android o iOS vaya cifrado hacia y desde el servidor que proporciona el servicio APIRest.
Por otro lado, también es muy recomendable e imprescindible, usar mecanismos de seguridad para validar el usuario, evitando que cualquier usuario de Internet pueda acceder al APIRest. Para ello se deben usar, además del mecanismo habitual de usuario y contraseña, tokens temporales (con expiración), que se generen en cada conexión y otros mecanismos para aumentar el nivel de seguridad.
Desarrollar aplicación multidispositivo para iOS y Android con acceso a base de datos externa en Delphi 12
Crearemos un nuevo proyecto en Delphi 12, desde «File» – «New» – «Multi-Device Application – Delphi»:

En la plantilla, elegiremos «Tabbed with Navigation», para crear una app base con pestañas y navegación entre ellas:

Guardaremos el proyecto en una carpeta del equipo:

Agregar componentes visuales a la app Android/iOS
Además de los componentes que Delphi ha añadido para mostrar una visualización tipo pestañas, añadiremos, a la pestaña inicial, el componente TGrid, de la paleta «Grids»:

Cambiaremos su propiedad propiedad «Name» a gridFacturas:

Hemos modificado las propiedades Text y Name del resto de componentes, para identificar cada uno con su uso.
Para este primer tutorial, mostraremos un listado de las facturas en un TGrid (gridFacturas) de la primera pestaña del TabControl principal (tabPrincipal) y un detalle de la factura seleccionada en el grid. Además, a modo de depuración, mostraremos el JSON original recibido del APIRest. Para ello, añadiremos un tercer tab al tabFacturas, seleccionando el tabFacturas (dentro del tabPrincipal) [1] y, dentro de este, seleccionando el tabFacturasGestion (dentro del tabFacturas) [2]. Pulsaremos con el botón derecho sobre el tabFacturasGestion y elegiremos «Add TabItem» [3]:

En la parte inferior del tabFacturasGestion nos aparecerán tres puntos, pulsando cada uno de ellos accederemos al tab correspondiente. Estos puntos sólo se muestran en tiempo de desarrollo, de diseño, para facilitar el movimiento entre tabs. Estos puntos no aparecen en tiempo de ejecución.

Nos posicionaremos en el último tab creado dentro del tabFacturasGestion y lo llamaremos «tabFacturasGestionDepuracion», añadiéndole un componente de tipo TMemo, de la paleta «Standard»:

Lo llamaremos txtJSONFacturas, con su propiedad «Align» a valor «Client», para que se adapte a la pantalla:

Al componte TGrid (gridFacturas), le agregaremos los campos (columnas) que queramos mostrar. Esto no es obligatorio, si no añadimos los campos manualmente, al conectar con el TClientDataset (como explicaremos más adelante), se crearán los campos que se pasen desde este componente automáticamente. Pero esto no es recomendable, dado que los campos no quedarán formateados en tamaño y posición.
Para añadir los campos manualmente al TGrid, sobre él, pulsaremos con el botón derecho. En el menú emergente elegiremos «Items Editor…»:

Añadiremos una columna de tipo TIntegerColumn para el campo «codigo» del JSON:

En las propiedades de esta columna podremos establecer su ancho (Width), el título que se mostrará al usuario (Header), el nombre para hacer referencia a ella en el código (si fuera necesario) Name y cualquier otra personalización que se quiera establecer:

Repetiremos el proceso para añadir las columnas:
- Cliente: de tipo TStringColumn.
- Importe: de tipo TCurrencyColumn.
- Fecha: de tipo TDateColumn.
De forma que el TGrid quedará con las cuatro columnas que queremos mostrar al usuario:

Quedando una estructura visual similar a la siguiente:

A modo de ejemplo, añadimos también varios componentes más para mostrar otras formas de visualizar la información, como por ejemplo un ListView y un ListBox. Añadiremos también varios botones al Toolbar del TabFacturasGestionListado para movernos a los tabs que contienen estos componentes:

Para el tab de detalle, añadiremos los TEdit correspondientes para mostrar la información de cada factura y los botones para movernos en las facturas (primera, última, anterior, siguiente):

Añadimos un ListView :

Y un ListBox:

En ambos componentes (ListBox y ListView) mostraremos todas las facturas y haremos interactivos los elementos, de forma que al pulsar sobre ellos nos muestre un mensaje con los datos del elemento pulsado.
Agregar componentes de acceso a datos para consumo de APIRest y código Pascal necesario
Agregaremos ahora los componentes de acceso a datos. Empezaremos por el TClientDataSet, de la paleta «Data Access». Este componente será el que «separe» los datos en JSON en columnas y registros:

Que llamaremos cdFacturas:

Añadiremos un componente TRESTResponse, de la paleta «REST Client»:

Que llamaremos restRespuesta:

Añadiremos un componente TRESTResponseDataSetAdapter, de la paleta «REST Client»:

Este componente, que hemos llamado restRespuestaDataset, será el que «enlace» el componente TRestResponse (restRespuesta), mediante su propiedad «ResponseJSON», con el componente TClientDataSet (cdFacturas), mediante la propiedad «Dataset»:

Añadiremos un componente TRESTClient, de la paleta «REST Client»:

Lo llamaremos restCliente y tendrá la propiedad BaseURL que será en la que le indiquemos la URL del servicio APIRest que queremos consumir (la estableceremos por código):

Añadiremos, por último, un componente TRESTRequest, de la paleta «REST Client»:

Que llamaremos restPeticion. Este componente se enlazará con el componente TRESTClient (restCliente) mediante su propiedad «Client» y el componente TRESTResponse (restRespuesta) mediante la propiedad Response:

A modo de depuración, añadiremos un componente visual más, un TLabel, en el tabFacturasGestionListado, para mostrar información de depuración (tiempo empleado en realizar la consulta al APIRest, número de registros devuelto, etc.):

Código fuente de cada evento y componente en Delphi/Pascal
En el siguiente enlace dejamos disponible el código fuente completo de la app multidispositivo (Windows, Android, iOS) de ejemplo de acceso APIRest a base de datos:
En el código de la unidad de nuestra aplicación, añadiremos las siguientes unidades al uses de la unidad UnidadGestionFacturas:
|
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 |
interface uses // System core System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Rtti, System.JSON, System.Actions, System.ImageList, // Database core Data.DB, Datasnap.DBClient, // Data binding core Data.Bind.DBScope, Data.Bind.EngExt, Data.Bind.Components, Data.Bind.Grid, Data.Bind.ObjectScope, // FMX binding FMX.Bind.DBEngExt, FMX.Bind.Editors, FMX.Bind.Grid, FMX.Bind.Navigator, // FMX core FMX.Types, FMX.Controls, FMX.Graphics, FMX.Forms, FMX.Dialogs, FMX.Layouts, FMX.Objects, FMX.StdCtrls, FMX.Controls.Presentation, // FMX Grid FMX.Grid, FMX.Grid.Style, // FMX ListView FMX.ListView, FMX.ListView.Appearances, FMX.ListView.Types, FMX.ListView.Adapters.Base, // FMX ListBox y otros FMX.TabControl, FMX.ListBox, FMX.Edit, FMX.Memo, FMX.Memo.Types, FMX.ScrollBox, // REST components REST.Client, REST.Types, REST.Response.Adapter, // Additional FMX.ActnList, FMX.ImgList, FMX.Gestures; type TformPrincipal = class(TForm) restCliente: TRESTClient; |
Es recomendable respetar el orden anterior de los uses, que hemos agrupado por tipo, para que quede más claro el uso de cada uno de ellos.

En el evento OnCreate del formulario principal de la app, añadiremos el siguiente código Delphi/Pascal:

|
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
procedure TformPrincipal.FormCreate(Sender: TObject); var vincularFuenteBD : TBindSourceDB; enlaceGridDataset : TLinkGridToDataSource; enlaceListViewDataset : TLinkGridToDataSource; begin try FBindingsList := TBindingsList.Create(Self); cdFacturas.DisableControls; // Activar el tab de facturas por defecto tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionListado; // Iniciamos la conexión con el servicio de APIRest para mostrar las facturas restRespuestaDataset.Response := restRespuesta; restRespuestaDataset.Dataset := cdFacturas; // Si el JSON incluye un elemento root para los registros de facturas la introduciremos aquí // En este caso, la información viene sin root restRespuestaDataset.RootElement := ''; // Establecemos la URL base del servidor APIRest, debe ser accesible desde Internet restCliente.BaseURL := 'http://ipcasa.ajpdsoft.com:8080/apirestfacturas'; { Establecemos la acción que queremos ejecutar del APIRest, en este caso "listar las facturas", correspondiente al fichero listar.php} restPeticion.Resource := 'listar.php'; // Realizamos la conexión con el servidor APIRest restPeticion.Execute; // Ordenamos el resultado obtenido en el Dataset por el campo código de factura cdFacturas.IndexFieldNames := 'codigo'; cdFacturas.First; //gridFacturas.BeginUpdate; // Para enlazar el componente visual TGrid con el Dataset de forma automática vincularFuenteBD := TBindSourceDB.Create(Self); vincularFuenteBD.DataSet := cdFacturas; enlaceGridDataset := TLinkGridToDataSource.Create(Self); enlaceGridDataset.Name := 'enlaceGridDataset'; enlaceGridDataset.DataSource := vincularFuenteBD; enlaceGridDataset.GridControl := gridFacturas; enlaceGridDataset.BindingsList := FBindingsList; enlaceGridDataset.Active := True; {$IFDEF ANDROID} gridFacturas.StyleLookup := 'gridstyle'; gridFacturas.ApplyStyleLookup; // Forzar redibujado gridFacturas.BeginUpdate; gridFacturas.Enabled := False; gridFacturas.Opacity := 0; gridFacturas.Visible := False; TThread.CreateAnonymousThread( procedure begin Sleep(100); // Pequeña pausa TThread.Synchronize(nil, procedure begin gridFacturas.Enabled := True; gridFacturas.Opacity := 1; gridFacturas.Visible := True; gridFacturas.BringToFront; gridFacturas.EndUpdate; end); end).Start; { // A veces necesario esperar un frame TThread.CreateAnonymousThread( procedure begin TThread.Synchronize(nil, procedure begin gridFacturas.Repaint; end); end).Start; } {$ENDIF} // Para enlazar el componente visual TListView con el Dataset de forma automática ListView1.BeginUpdate; enlaceListViewDataset := TLinkGridToDataSource.Create(Self); enlaceListViewDataset.DataSource := vincularFuenteBD; enlaceListViewDataset.GridControl := ListView1; enlaceListViewDataset.BindingsList := FBindingsList; enlaceListViewDataset.Active := True; ListView1.Align := TAlignLayout.Client; ListView1.ItemAppearanceName := 'ListItem'; ListView1.ItemEditAppearanceName := 'ListItem'; ListView1.ItemAppearanceObjects.ItemObjects.Text.Visible := True; ListView1.ItemAppearanceObjects.ItemObjects.Detail.Visible := True; // Estilo específico para Android {$IFDEF ANDROID} ListView1.ItemAppearanceObjects.ItemObjects.Text.Font.Size := 16; ListView1.ItemAppearanceObjects.ItemObjects.Detail.Font.Size := 14; {$ENDIF} ListView1.EndUpdate; // Habilitar evento de selección ListView1.OnItemClick := ListView1ItemClick; lbInfo.Text := lbInfo.Text + ' | Facturas: ' + cdFacturas.RecordCount.ToString; cdFacturas.EnableControls; except on E: Exception do ShowMessage('Error al conectar a la base de datos. Revise la conexión a Internet en el dispositivo: ' + #13#10 + #13#10 + E.Message); end; end; |
En el evento OnAfterExecute de restPeticion añadiremos el siguiente código Delphi/Pascal:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
procedure TformPrincipal.restPeticionAfterExecute( Sender: TCustomRESTRequest); begin txtJSONFacturas.Lines.Clear; lbInfo.Text := 'T: ' + IntToStr(Sender.ExecutionPerformance.TotalExecutionTime) + 'ms'; if assigned(restRespuesta.JSONValue) then begin txtJSONFacturas.Lines.Text := restRespuesta.JSONValue.Format(); end else txtJSONFacturas.Lines.Add(restRespuesta.Content); end; |
En el evento OnHTTPProtocolError del restPeticion añadiremos el siguiente código Delphi/Pascal:
|
1 2 3 4 5 6 7 |
procedure TformPrincipal.restPeticionHTTPProtocolError( Sender: TCustomRESTRequest); begin // Mostrar el posible error en el Memo del depurador JSON txtJSONFacturas.Lines.Add(Sender.Response.StatusText); txtJSONFacturas.Lines.Add(Sender.Response.Content); end; |
En el evento OnAfterScroll del cdFacturas añadiremos el siguiente código Delphi/Pascal:
|
1 2 3 4 5 6 7 8 9 10 |
// Cuando nos movemos de registro, añadimos los nuevos datos en los TEdit de detalle if cdFacturas.Active then begin txtFacturaCodigo.Text := cdFacturascodigo.AsString; txtFacturaCliente.Text := cdFacturascliente.AsString; txtFacturaImporte.Text := cdFacturasimporte.AsString; txtFacturaFecha.Text := cdFacturasfecha.AsString; // Modificamos el label del título del tab de detalle de factura lbDetalleFactura.Text := 'Factura ' + cdFacturascodigo.AsString; end; |
En el evento OnCellDblClick del gridFacturas añadiremos el siguiente código Delphi/Pascal:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TformPrincipal.gridFacturasCellDblClick( const Column: TColumn; const Row: Integer); begin // Obtén el índice de la fila seleccionada var itemSeleccionado := gridFacturas.Selected; //ShowMessage(grid); // Verifica que el índice sea válido if (itemSeleccionado >= 0) and (itemSeleccionado < cdFacturas.RecordCount) then begin // Mueve el cursor del DataSet al registro correspondiente cdFacturas.RecNo := itemSeleccionado + 1; // Mostramos el detalle de la factura seleccionada actFacturaDetalleExecute(nil); end; end; |
Añadiremos los siguientes actions en el ActionList (que asignaremos a cada botón de acción):

Para la acción de mostrar el tab inicial con el listado de facturas en el TGrid:
|
1 2 3 4 5 |
procedure TformPrincipal.actFacturaListadoExecute(Sender: TObject); begin tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionListado; end; |
Para la acción de mostrar el tab de detalle de la factura actual (seleccionada en el TGrid):
|
1 2 3 4 5 |
procedure TformPrincipal.actFacturaDetalleExecute(Sender: TObject); begin tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionDetalle; end; |
Para la acción de mostrar el tab de depuración con un TMemo con el JSON obtenido:
|
1 2 3 4 5 |
procedure TformPrincipal.actDepurarExecute(Sender: TObject); begin tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionDepurarJSON; end; |
Para la acción de mostrar el tab con el listado de facturas en el ListBox formateado e interactivo (muestra una imagen que se puede habilitar o deshabilitar pulsándola, una imagen por cada factura, los datos de la factura y un botón de acción para cada factura):
|
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 |
procedure TformPrincipal.actFacturaListBoxExecute(Sender: TObject); var //posActual : integer; Item: TListBoxItem; importe : string; begin lsBoxFacturas.BeginUpdate; lsBoxFacturas.Clear; // Para que no ejecute eventos de cambio en otros controles mientras se recorre cdFacturas.DisableControls; // Si queremos guardar la posición actual (registro seleccionado), para dejarla igual al finalizar //posActual := cdFacturas.RecNo; cdFacturas.First; while not cdFacturas.Eof do begin // create custom item Item := TListBoxItem.Create(nil); Item.Parent := lsBoxFacturas; Item.StyleLookup := 'stFacturaListBoxItem'; Item.StylesData['cliente'] := cdFacturas.FieldByName('cliente').AsString; Item.StylesData['codigo'] := cdFacturas.FieldByName('codigo').AsString; // Ejemplo para mostrar una imagen en cada elemento del ListBox // Si es item primo mostramos la imagen 1 del imgListBox if Odd(Item.Index) then Item.ItemData.Bitmap := imgListBox.MultiResBitmap.Items[0].Bitmap else // Si no mostramos la imagen 2 Item.ItemData.Bitmap := imgListBox.MultiResBitmap.Items[1].Bitmap; importe := formatfloat('###,###,###,##0.00€;-###,###,###,##0.00;0€', cdFacturasimporte.AsFloat); Item.StylesData['importe'] := importe; // Ejemplo para mostrar habiltada o no la imagen de cada elemento del ListBox // Aquí podríamos habilitar/deshabilitar en función de un valor obtenido del registro actual // Como ejemplo, estableceremos True para los elementos pares if not Odd(Item.Index) then Item.StylesData['cobrada'] := true else Item.StylesData['cobrada'] := false; // Asignamos el procedimiento que se ejecutará si se pulsa en la imagen check de cada elemento Item.StylesData['cobrada.OnClick'] := TValue.From<TNotifyEvent>(CheckListBoxPulsado); // Asignamos el procedimiento que se ejecutará si se pulsa en el botón + Info de cada elemento Item.StylesData['btFacturaListBoxInfo.OnClick'] := TValue.From<TNotifyEvent>(BotonListBoxPulsado); cdFacturas.Next; end; lsBoxFacturas.EndUpdate; // Para dejar la posición actual antes de llenar el ListView //cdFacturas.RecNo := posActual; cdFacturas.EnableControls; tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionListBox; end; |
Para los eventos definidos en el ListBox (BotonListPulsado y CheckListBoxPulsado) añadiremos el siguiente código Delphi/Pascal:
|
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 |
// Para mostrar item en ListBox function FindItemParent(Obj: TFmxObject; ParentClass: TClass): TFmxObject; begin Result := nil; if Assigned(Obj.Parent) then if Obj.Parent.ClassType = ParentClass then Result := Obj.Parent else Result := FindItemParent(Obj.Parent, ParentClass); end; // Evento click en botón de un Item del ListBox procedure TformPrincipal.BotonListBoxPulsado(Sender: TObject); var Item : TListBoxItem; begin Item := TListBoxItem(FindItemParent(Sender as TFmxObject,TListBoxItem)); if Assigned(Item) then begin // Posicionamos el Dataset en el registro seleccionado en el ListBox cdFacturas.RecNo := Item.Index + 1; var importe : string; importe := formatfloat('###,###,###,##0.00€;-###,###,###,##0.00;0€', cdFacturasimporte.AsFloat); ShowMessage('Código: ' + cdFacturascodigo.AsString + chr(13) + 'Cliente: ' + cdFacturascliente.AsString + chr(13) + 'Importe: ' + importe + chr(13) + 'Fecha: ' + cdFacturasfecha.AsString); end; end; //Evento click en check de un elemento del ListBox procedure TformPrincipal.CheckListBoxPulsado(Sender: TObject); var Item : TListBoxItem; begin Item := TListBoxItem(FindItemParent(Sender as TFmxObject,TListBoxItem)); if Assigned(Item) then ShowMessage('Check cambiado de valor del item ' + IntToStr(Item.Index) + ', cambiado a valor ' + BoolToStr(not Item.StylesData['cobrada'].AsBoolean, true)); end; |
Para la acción de mostrar el tab con el listado de facturas en el ListView interactivo, al pulsar sobre una factura nos mostrará un mensaje con sus datos:
|
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 |
procedure TformPrincipal.actFacturaListViewExecute(Sender: TObject); var posActual : integer; Item: TListViewItem; begin //CargarFacturasEnListView; MostrarDatosEnListView; tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionListView; end; // Muestra las facturas en el ListView procedure TformPrincipal.CargarFacturasEnListView; var Item: TListViewItem; posActual: Integer; begin ListView1.BeginUpdate; try ListView1.Items.Clear; // Guardar posición actual posActual := cdFacturas.RecNo; cdFacturas.DisableControls; try cdFacturas.First; while not cdFacturas.Eof do begin Item := ListView1.Items.Add; // Primera línea (Texto principal) Item.Text := cdFacturas.FieldByName('codigo').AsString; // Segunda línea (Detalle) Item.Detail := Format('Cliente: %s | Importe: %s€', [ cdFacturas.FieldByName('cliente').AsString, FormatFloat('0.00', cdFacturas.FieldByName('importe').AsFloat) ]); // Almacenar ID u otros datos para referencia Item.Tag := cdFacturas.RecNo; // Configuración visual adicional {$IFDEF ANDROID} Item.Purpose := TListItemPurpose.None; {$ENDIF} cdFacturas.Next; end; finally cdFacturas.RecNo := posActual; cdFacturas.EnableControls; end; finally ListView1.EndUpdate; // Fuerza actualización visual en Android TThread.Queue(nil, procedure begin ListView1.RebuildList; end); end; end; |
Para los botones de navegación de facturas (en el tab de detalle), en sus respectivos eventos OnClick, añadiremos el siguiente código Delphi/Pascal:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
procedure TformPrincipal.btFacturaMoverAnteriorClick(Sender: TObject); begin cdFacturas.Prior; end; procedure TformPrincipal.btFacturaMoverPrimeroClick( Sender: TObject); begin cdFacturas.First; end; procedure TformPrincipal.btFacturaMoverSiguienteClick(Sender: TObject); begin cdFacturas.Next; end; procedure TformPrincipal.btFacturaMoverUltimoClick(Sender: TObject); begin cdFacturas.Last; end; |
En el siguiente enlace incluimos el código fuente completo de la app multidispositivo con Delphi y Firemonkey:
Generación de APK, instalación en Android y funcionamiento de app con acceso a MySQL mediante APIRest
Si compilamos la aplicación multidispositivo elgiendo «Android 64-bit», para generar el APK para Android (en este enlace dejamos disponible el código fuente completo y el fichero APK de instalación en Android):

Se iniciará la compilación y generación del APK de instalación de la app en dispositivos smartphones Android:

Se habrá generado el apk, que enviaremos al móvil, por ejemplo por Telegram:

Descargaremos el apk en el móvil:

Pulsaremos en «Instalar»:

Pulsaremos en «Instalar sin analizar»:

Pulsaremos en «Abrir» una vez instalada:

La app se iniciará y mostrará en el grid las facturas con sus datos. Si hacemos doble pulsación en una de las facturas:

Nos mostrará su detalle. Desde este tab podremos movernos a la factura siguiente, a la anterior, a la primera o a la última. Incluso podremos movernos con gestos de deslizamiento a derecha y a izquierda:

Pulsando en el botón de «ListView», nos mostrará un tab con las facturas en formato de vista ListView:

Si pulsamos en una de las facturas, nos mostrará un mensaje con los datos de la factura pulsada:

De la misma forma obtendremos la vista del tab en formato ListBox:

Si pulsamos en el botón «+ Info» de una de las facturas del ListBox, nos mostrará un mensaje con sus datos:

Y en el botón de JSON Depurar, nos mostrará un tab con un Memo que contiene las facturas obtenidas en formato JSON (útil para depuración):

Anexo
Código fuente completo de la unidad UnidadGestionFacturas.pas
A continuación, mostramos el código fuente completo de la unidad de la aplicación multidispositivo en Delphi y Firemonkey, también incluimos enlace a la descarga de todo el proyecto Delphi 12 Community Edition:
|
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 |
unit UnidadGestionFacturas; interface uses // System core System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Rtti, System.JSON, System.Actions, System.ImageList, // Database core Data.DB, Datasnap.DBClient, // Data binding core Data.Bind.DBScope, Data.Bind.EngExt, Data.Bind.Components, Data.Bind.Grid, Data.Bind.ObjectScope, // FMX binding FMX.Bind.DBEngExt, FMX.Bind.Editors, FMX.Bind.Grid, FMX.Bind.Navigator, // FMX core FMX.Types, FMX.Controls, FMX.Graphics, FMX.Forms, FMX.Dialogs, FMX.Layouts, FMX.Objects, FMX.StdCtrls, FMX.Controls.Presentation, // FMX Grid FMX.Grid, FMX.Grid.Style, // FMX ListView FMX.ListView, FMX.ListView.Appearances, FMX.ListView.Types, FMX.ListView.Adapters.Base, // FMX ListBox y otros FMX.TabControl, FMX.ListBox, FMX.Edit, FMX.Memo, FMX.Memo.Types, FMX.ScrollBox, // REST components REST.Client, REST.Types, REST.Response.Adapter, // Additional FMX.ActnList, FMX.ImgList, FMX.Gestures; type TformPrincipal = class(TForm) restCliente: TRESTClient; restRespuesta: TRESTResponse; restPeticion: TRESTRequest; restRespuestaDataset: TRESTResponseDataSetAdapter; ImageList1: TImageList; cdFacturas: TClientDataSet; cdFacturascodigo: TIntegerField; cdFacturascliente: TStringField; cdFacturasimporte: TCurrencyField; cdFacturasfecha: TDateField; lbInfo: TLabel; tabPrincipal: TTabControl; tabFacturas: TTabItem; tabFacturasGestion: TTabControl; tabFacturasGestionListado: TTabItem; ToolBar1: TToolBar; lblTitle1: TLabel; btFacturaDetalle: TSpeedButton; btDepurar: TSpeedButton; btFacturasListBox: TSpeedButton; tabFacturasGestionDetalle: TTabItem; ToolBar2: TToolBar; lbDetalleFactura: TLabel; tabFacturasGestionDepurarJSON: TTabItem; Z: TLabel; ToolBar6: TToolBar; Label2: TLabel; txtJSONFacturas: TMemo; tabFacturasGestionListBox: TTabItem; ToolBar7: TToolBar; Label3: TLabel; tabClientes: TTabItem; ToolBar3: TToolBar; lblTitle3: TLabel; tabPresupuestos: TTabItem; ToolBar4: TToolBar; lblTitle4: TLabel; tabStock: TTabItem; ToolBar5: TToolBar; lblTitle5: TLabel; gridFacturas: TGrid; gridCodigo: TIntegerColumn; gridCliente: TStringColumn; gridImporte: TCurrencyColumn; gridFecha: TDateColumn; ActionList1: TActionList; actFacturaSiguiente: TNextTabAction; actFacturaVolver: TPreviousTabAction; SpeedButton4: TSpeedButton; SpeedButton7: TSpeedButton; GestureManager1: TGestureManager; actFacturaListado: TAction; actFacturaDetalle: TAction; SpeedButton1: TSpeedButton; actDepurar: TAction; actFacturaListBox: TAction; layoutFacturaDetalle: TVertScrollBox; Label1: TLabel; txtFacturaCodigo: TEdit; Label4: TLabel; txtFacturaCliente: TEdit; Label5: TLabel; txtFacturaImporte: TEdit; Label6: TLabel; txtFacturaFecha: TEdit; btFacturaMoverPrimero: TSpeedButton; btFacturaMoverAnterior: TSpeedButton; btFacturaMoverSiguiente: TSpeedButton; btFacturaMoverUltimo: TSpeedButton; Resources1: TStyleBook; imgListBox: TImage; tabFacturasGestionListView: TTabItem; ToolBar8: TToolBar; Label7: TLabel; SpeedButton2: TSpeedButton; actFacturaListView: TAction; btFacturasListView: TSpeedButton; lsBoxFacturas: TListBox; ListView1: TListView; procedure FormCreate(Sender: TObject); procedure cdFacturasAfterScroll(DataSet: TDataSet); procedure restPeticionHTTPProtocolError(Sender: TCustomRESTRequest); procedure restPeticionAfterExecute(Sender: TCustomRESTRequest); procedure btFacturaMoverPrimeroClick(Sender: TObject); procedure tabPrincipalGesture(Sender: TObject; const EventInfo: TGestureEventInfo; var Handled: Boolean); procedure FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: WideChar; Shift: TShiftState); procedure actFacturaListadoExecute(Sender: TObject); procedure actFacturaDetalleExecute(Sender: TObject); procedure gridFacturasCellDblClick(const Column: TColumn; const Row: Integer); procedure actDepurarExecute(Sender: TObject); procedure actFacturaListBoxExecute(Sender: TObject); procedure btFacturaMoverUltimoClick(Sender: TObject); procedure btFacturaMoverSiguienteClick(Sender: TObject); procedure btFacturaMoverAnteriorClick(Sender: TObject); procedure ListView1ItemClick(const Sender: TObject; const AItem: TListViewItem); procedure BotonListBoxPulsado(Sender: TObject); procedure CheckListBoxPulsado(Sender: TObject); procedure actFacturaListViewExecute(Sender: TObject); procedure CargarFacturasEnListView; procedure MostrarDatosEnListView; procedure layoutFacturaDetalleGesture(Sender: TObject; const EventInfo: TGestureEventInfo; var Handled: Boolean); private { Private declarations } FBindingsList: TBindingsList; public { Public declarations } end; var formPrincipal: TformPrincipal; implementation {$R *.fmx} {$R *.LgXhdpiPh.fmx ANDROID} {$R *.iPhone4in.fmx IOS} {$R *.NmXhdpiPh.fmx ANDROID} {$R *.SmXhdpiPh.fmx ANDROID} procedure TformPrincipal.actDepurarExecute(Sender: TObject); begin tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionDepurarJSON; end; procedure TformPrincipal.actFacturaDetalleExecute(Sender: TObject); begin tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionDetalle; end; procedure TformPrincipal.actFacturaListadoExecute(Sender: TObject); begin tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionListado; end; procedure TformPrincipal.actFacturaListBoxExecute(Sender: TObject); var //posActual : integer; Item: TListBoxItem; importe : string; begin lsBoxFacturas.BeginUpdate; lsBoxFacturas.Clear; // Para que no ejecute eventos de cambio en otros controles mientras se recorre cdFacturas.DisableControls; // Si queremos guardar la posición actual (registro seleccionado), para dejarla igual al finalizar //posActual := cdFacturas.RecNo; cdFacturas.First; while not cdFacturas.Eof do begin // create custom item Item := TListBoxItem.Create(nil); Item.Parent := lsBoxFacturas; Item.StyleLookup := 'stFacturaListBoxItem'; Item.StylesData['cliente'] := cdFacturas.FieldByName('cliente').AsString; Item.StylesData['codigo'] := cdFacturas.FieldByName('codigo').AsString; // Ejemplo para mostrar una imagen en cada elemento del ListBox // Si es item primo mostramos la imagen 1 del imgListBox if Odd(Item.Index) then Item.ItemData.Bitmap := imgListBox.MultiResBitmap.Items[0].Bitmap else // Si no mostramos la imagen 2 Item.ItemData.Bitmap := imgListBox.MultiResBitmap.Items[1].Bitmap; importe := formatfloat('###,###,###,##0.00€;-###,###,###,##0.00;0€', cdFacturasimporte.AsFloat); Item.StylesData['importe'] := importe; // Ejemplo para mostrar habiltada o no la imagen de cada elemento del ListBox // Aquí podríamos habilitar/deshabilitar en función de un valor obtenido del registro actual // Como ejemplo, estableceremos True para los elementos pares if not Odd(Item.Index) then Item.StylesData['cobrada'] := true else Item.StylesData['cobrada'] := false; // Asignamos el procedimiento que se ejecutará si se pulsa en la imagen check de cada elemento Item.StylesData['cobrada.OnClick'] := TValue.From<TNotifyEvent>(CheckListBoxPulsado); // Asignamos el procedimiento que se ejecutará si se pulsa en el botón + Info de cada elemento Item.StylesData['btFacturaListBoxInfo.OnClick'] := TValue.From<TNotifyEvent>(BotonListBoxPulsado); cdFacturas.Next; end; lsBoxFacturas.EndUpdate; // Para dejar la posición actual antes de llenar el ListView //cdFacturas.RecNo := posActual; cdFacturas.EnableControls; tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionListBox; end; // Para mostrar item en ListBox function FindItemParent(Obj: TFmxObject; ParentClass: TClass): TFmxObject; begin Result := nil; if Assigned(Obj.Parent) then if Obj.Parent.ClassType = ParentClass then Result := Obj.Parent else Result := FindItemParent(Obj.Parent, ParentClass); end; // Evento click en botón de un Item del ListBox procedure TformPrincipal.BotonListBoxPulsado(Sender: TObject); var Item : TListBoxItem; begin Item := TListBoxItem(FindItemParent(Sender as TFmxObject,TListBoxItem)); if Assigned(Item) then begin // Posicionamos el Dataset en el registro seleccionado en el ListBox cdFacturas.RecNo := Item.Index + 1; var importe : string; importe := formatfloat('###,###,###,##0.00€;-###,###,###,##0.00;0€', cdFacturasimporte.AsFloat); ShowMessage('Código: ' + cdFacturascodigo.AsString + chr(13) + 'Cliente: ' + cdFacturascliente.AsString + chr(13) + 'Importe: ' + importe + chr(13) + 'Fecha: ' + cdFacturasfecha.AsString); end; end; procedure TformPrincipal.MostrarDatosEnListView; begin // 1. Configuración básica del ListView ListView1.BeginUpdate; try // Limpiar cualquier contenido previo ListView1.Items.Clear; // Establecer el estilo de visualización básico ListView1.ItemAppearanceName := 'ImageListItemBottomDetailShowCheck'; // Estilo predefinido más simple cdFacturas.DisableControls; cdFacturas.First; while not cdFacturas.Eof do begin with ListView1.Items.Add do begin Text := cdFacturasfecha.AsString + ' ' + cdFacturascliente.AsString; // Texto principal (primera línea) var importe : string; importe := formatfloat('###,###,###,##0.00€;-###,###,###,##0.00;0€', cdFacturasimporte.AsFloat); Detail := Format('Importe: %s Código: %s', [importe, cdFacturascodigo.AsString]); Tag := cdFacturascodigo.AsInteger; end; cdFacturas.Next; end; {$IFDEF ANDROID} ListView1.ItemAppearanceObjects.ItemObjects.Text.Font.Size := 16; ListView1.ItemAppearanceObjects.ItemObjects.Detail.Font.Size := 14; {$ENDIF} finally ListView1.EndUpdate; end; //cdFacturas.EnableControls; // 4. Forzar actualización visual (especialmente importante en Android) //ListView1.RebuildList; end; // Muestra las facturas en el ListView procedure TformPrincipal.CargarFacturasEnListView; var Item: TListViewItem; posActual: Integer; begin ListView1.BeginUpdate; try ListView1.Items.Clear; // Guardar posición actual posActual := cdFacturas.RecNo; cdFacturas.DisableControls; try cdFacturas.First; while not cdFacturas.Eof do begin Item := ListView1.Items.Add; // Primera línea (Texto principal) Item.Text := cdFacturas.FieldByName('codigo').AsString; // Segunda línea (Detalle) Item.Detail := Format('Cliente: %s | Importe: %s€', [ cdFacturas.FieldByName('cliente').AsString, FormatFloat('0.00', cdFacturas.FieldByName('importe').AsFloat) ]); // Almacenar ID u otros datos para referencia Item.Tag := cdFacturas.RecNo; // Configuración visual adicional {$IFDEF ANDROID} Item.Purpose := TListItemPurpose.None; {$ENDIF} cdFacturas.Next; end; finally cdFacturas.RecNo := posActual; cdFacturas.EnableControls; end; finally ListView1.EndUpdate; // Fuerza actualización visual en Android TThread.Queue(nil, procedure begin ListView1.RebuildList; end); end; end; //Evento click en check de un elemento del ListBox procedure TformPrincipal.CheckListBoxPulsado(Sender: TObject); var Item : TListBoxItem; begin Item := TListBoxItem(FindItemParent(Sender as TFmxObject,TListBoxItem)); if Assigned(Item) then ShowMessage('Check cambiado de valor del item ' + IntToStr(Item.Index) + ', cambiado a valor ' + BoolToStr(not Item.StylesData['cobrada'].AsBoolean, true)); end; procedure TformPrincipal.actFacturaListViewExecute(Sender: TObject); begin MostrarDatosEnListView; tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionListView; end; procedure TformPrincipal.btFacturaMoverAnteriorClick(Sender: TObject); begin cdFacturas.Prior; end; procedure TformPrincipal.btFacturaMoverPrimeroClick( Sender: TObject); begin cdFacturas.First; end; procedure TformPrincipal.btFacturaMoverSiguienteClick(Sender: TObject); begin cdFacturas.Next; end; procedure TformPrincipal.btFacturaMoverUltimoClick(Sender: TObject); begin cdFacturas.Last; end; procedure TformPrincipal.cdFacturasAfterScroll(DataSet: TDataSet); begin // Cuando nos movemos de registro, añadimos los nuevos datos en los TEdit de detalle if cdFacturas.Active then begin txtFacturaCodigo.Text := cdFacturascodigo.AsString; txtFacturaCliente.Text := cdFacturascliente.AsString; txtFacturaImporte.Text := cdFacturasimporte.AsString; txtFacturaFecha.Text := cdFacturasfecha.AsString; // Modificamos el label del título del tab de detalle de factura lbDetalleFactura.Text := 'Factura ' + cdFacturascodigo.AsString; end; end; procedure TformPrincipal.FormCreate(Sender: TObject); var vincularFuenteBD : TBindSourceDB; enlaceGridDataset : TLinkGridToDataSource; enlaceListViewDataset : TLinkGridToDataSource; begin try FBindingsList := TBindingsList.Create(Self); cdFacturas.DisableControls; // Activar el tab de facturas por defecto tabPrincipal.ActiveTab := tabFacturas; tabFacturasGestion.ActiveTab := tabFacturasGestionListado; // Iniciamos la conexión con el servicio de APIRest para mostrar las facturas restRespuestaDataset.Response := restRespuesta; restRespuestaDataset.Dataset := cdFacturas; // Si el JSON incluye un elemento root para los registros de facturas la introduciremos aquí // En este caso, la información viene sin root restRespuestaDataset.RootElement := ''; // Establecemos la URL base del servidor APIRest, debe ser accesible desde Internet restCliente.BaseURL := 'http://ipcasa.ajpdsoft.com:8080/apirestfacturas'; { Establecemos la acción que queremos ejecutar del APIRest, en este caso "listar las facturas", correspondiente al fichero listar.php} restPeticion.Resource := 'listar.php'; // Realizamos la conexión con el servidor APIRest restPeticion.Execute; // Ordenamos el resultado obtenido en el Dataset por el campo código de factura cdFacturas.IndexFieldNames := 'codigo'; cdFacturas.First; //gridFacturas.BeginUpdate; // Para enlazar el componente visual TGrid con el Dataset de forma automática vincularFuenteBD := TBindSourceDB.Create(Self); vincularFuenteBD.DataSet := cdFacturas; enlaceGridDataset := TLinkGridToDataSource.Create(Self); enlaceGridDataset.Name := 'enlaceGridDataset'; enlaceGridDataset.DataSource := vincularFuenteBD; enlaceGridDataset.GridControl := gridFacturas; enlaceGridDataset.BindingsList := FBindingsList; enlaceGridDataset.Active := True; {$IFDEF ANDROID} gridFacturas.StyleLookup := 'gridstyle'; gridFacturas.ApplyStyleLookup; // Forzar redibujado gridFacturas.BeginUpdate; gridFacturas.Enabled := False; gridFacturas.Opacity := 0; gridFacturas.Visible := False; TThread.CreateAnonymousThread( procedure begin Sleep(100); // Pequeña pausa TThread.Synchronize(nil, procedure begin gridFacturas.Enabled := True; gridFacturas.Opacity := 1; gridFacturas.Visible := True; gridFacturas.BringToFront; gridFacturas.EndUpdate; end); end).Start; { // A veces necesario esperar un frame TThread.CreateAnonymousThread( procedure begin TThread.Synchronize(nil, procedure begin gridFacturas.Repaint; end); end).Start; } {$ENDIF} // Para enlazar el componente visual TListView con el Dataset de forma automática ListView1.BeginUpdate; enlaceListViewDataset := TLinkGridToDataSource.Create(Self); enlaceListViewDataset.DataSource := vincularFuenteBD; enlaceListViewDataset.GridControl := ListView1; enlaceListViewDataset.BindingsList := FBindingsList; enlaceListViewDataset.Active := True; ListView1.Align := TAlignLayout.Client; ListView1.ItemAppearanceName := 'ListItem'; ListView1.ItemEditAppearanceName := 'ListItem'; ListView1.ItemAppearanceObjects.ItemObjects.Text.Visible := True; ListView1.ItemAppearanceObjects.ItemObjects.Detail.Visible := True; // Estilo específico para Android {$IFDEF ANDROID} ListView1.ItemAppearanceObjects.ItemObjects.Text.Font.Size := 16; ListView1.ItemAppearanceObjects.ItemObjects.Detail.Font.Size := 14; {$ENDIF} ListView1.EndUpdate; // Habilitar evento de selección ListView1.OnItemClick := ListView1ItemClick; lbInfo.Text := lbInfo.Text + ' | Facturas: ' + cdFacturas.RecordCount.ToString; cdFacturas.EnableControls; except on E: Exception do ShowMessage('Error al conectar a la base de datos. Revise la conexión a Internet en el dispositivo: ' + #13#10 + #13#10 + E.Message); end; end; procedure TformPrincipal.FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: WideChar; Shift: TShiftState); begin // Ejemplo para capturar la pulsación de un botón del móvil // Por ejemplo el botón anterior/atrás { if Key = vkHardwareBack then begin // Si estamos en el tab de facturas y estamos en algún tab diferente al inicial (Listado) // Movemos un tab atrás if (tabPrincipal.ActiveTab = tabFacturas) and ((tabFacturasGestion.ActiveTab = tabFacturasGestionDetalle) or (tabFacturasGestion.ActiveTab = tabFacturasGestionListBox) or (tabFacturasGestion.ActiveTab = tabFacturasGestionListView) or (tabFacturasGestion.ActiveTab = tabFacturasGestionDepurarJSON)) then begin tabFacturasGestion.Previous; Key := 0; end; end; } end; procedure TformPrincipal.gridFacturasCellDblClick( const Column: TColumn; const Row: Integer); begin // Obtén el índice de la fila seleccionada var itemSeleccionado := gridFacturas.Selected; // Verifica que el índice sea válido if (itemSeleccionado >= 0) and (itemSeleccionado < cdFacturas.RecordCount) then begin // Mueve el cursor del DataSet al registro correspondiente cdFacturas.RecNo := itemSeleccionado + 1; // Mostramos el detalle de la factura seleccionada actFacturaDetalleExecute(nil); end; end; procedure TformPrincipal.layoutFacturaDetalleGesture(Sender: TObject; const EventInfo: TGestureEventInfo; var Handled: Boolean); begin // Capturamos los gestos case EventInfo.GestureID of // Gesto desplazamiento a la izquierda sgiLeft: begin // Si estamos en el tab de detalle de la factura, nos posicionamos en el registro siguiente if (tabPrincipal.ActiveTab = tabFacturas) and (tabFacturasGestion.ActiveTab = tabFacturasGestionDetalle) then begin cdFacturas.Next; end; Handled := True; end; // Gesto desplazamiento a la derecha sgiRight: begin // Ejemplo si queremos ir al tab siguiente en el tab principal { if tabPrincipal.ActiveTab <> tabPrincipal.Tabs[0] then tabPrincipal.ActiveTab := tabPrincipal.Tabs[tabPrincipal.TabIndex - 1]; } // Ejemplo para desplazarnos a un tab concreto del taba de facturación en función del tab en el que estamos { if (tabPrincipal.ActiveTab = tabFacturas) and ((tabFacturasGestion.ActiveTab = tabFacturasGestionDetalle) or (tabFacturasGestion.ActiveTab = tabFacturasGestionListBox) or (tabFacturasGestion.ActiveTab = tabFacturasGestionListView) or (tabFacturasGestion.ActiveTab = tabFacturasGestionDepurarJSON)) then } // Si estamos en el tab de detalle de la factura, nos posicionamos en el registro anterior if (tabPrincipal.ActiveTab = tabFacturas) and (tabFacturasGestion.ActiveTab = tabFacturasGestionDetalle) then begin cdFacturas.Prior; end; Handled := True; end; end; end; // Si se pulsa en un elemento del ListView procedure TformPrincipal.ListView1ItemClick(const Sender: TObject; const AItem: TListViewItem); begin if Assigned(AItem) then begin // Posicionamos el Dataset en el registro seleccionado en el ListView var itemSeleccionado := ListView1.ItemIndex; if itemSeleccionado >= 0 then begin cdFacturas.Recno := itemSeleccionado + 1; var importe : string; importe := formatfloat('###,###,###,##0.00€;-###,###,###,##0.00;0€', cdFacturasimporte.AsFloat); ShowMessage('Código: ' + cdFacturascodigo.AsString + chr(13) + 'Cliente: ' + cdFacturascliente.AsString + chr(13) + 'Importe: ' + importe + chr(13) + 'Fecha: ' + cdFacturasfecha.AsString); end; end; end; procedure TformPrincipal.restPeticionAfterExecute( Sender: TCustomRESTRequest); begin txtJSONFacturas.Lines.Clear; // Mostramos el tiempo de ejecución (para depurar) lbInfo.Text := 'T: ' + IntToStr(Sender.ExecutionPerformance.TotalExecutionTime) + 'ms'; if assigned(restRespuesta.JSONValue) then begin txtJSONFacturas.Lines.Text := restRespuesta.JSONValue.Format(); end else txtJSONFacturas.Lines.Add(restRespuesta.Content); end; procedure TformPrincipal.restPeticionHTTPProtocolError( Sender: TCustomRESTRequest); begin // Mostrar el posible error en el Memo del depurador JSON txtJSONFacturas.Lines.Add(Sender.Response.StatusText); txtJSONFacturas.Lines.Add(Sender.Response.Content); end; procedure TformPrincipal.tabPrincipalGesture(Sender: TObject; const EventInfo: TGestureEventInfo; var Handled: Boolean); begin // Ejemplo para gestos adelante/atrás en tabprincipal { case EventInfo.GestureID of sgiLeft: begin if tabPrincipal.ActiveTab <> tabPrincipal.Tabs[tabPrincipal.TabCount - 1] then tabPrincipal.ActiveTab := tabPrincipal.Tabs[tabPrincipal.TabIndex + 1]; Handled := True; end; sgiRight: begin if tabPrincipal.ActiveTab <> tabPrincipal.Tabs[0] then tabPrincipal.ActiveTab := tabPrincipal.Tabs[tabPrincipal.TabIndex - 1]; Handled := True; end; end; } end; end. |
Posibles errores y su solución
Exception EBindCompError in module GestionFacturas.exe at 00000000009D65D7.
Error in . No list control editor available for TGrid.
Este error es debido a que no hemos agregado la siguiente unidad al uses:
|
1 |
Fmx.Bind.Editors; |
En el TGrid aparecen los registros pero sin datos.
Esto suele ser debido a que falta algún uses o bien a que no están dispuestos en el orden correcto. Estos son los uses necesarios para un TGrid y su enlace a datos, así como para mostrar otros componentes como TabControl, ScrollBox, Memo, ListView y ListBox:
|
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 |
uses // System core System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Rtti, System.JSON, System.Actions, System.ImageList, // Database core Data.DB, Datasnap.DBClient, // Data binding core Data.Bind.DBScope, Data.Bind.EngExt, Data.Bind.Components, Data.Bind.Grid, Data.Bind.ObjectScope, // FMX binding FMX.Bind.DBEngExt, FMX.Bind.Editors, FMX.Bind.Grid, FMX.Bind.Navigator, // FMX core FMX.Types, FMX.Controls, FMX.Graphics, FMX.Forms, FMX.Dialogs, FMX.Layouts, FMX.Objects, FMX.StdCtrls, FMX.Controls.Presentation, // FMX Grid FMX.Grid, FMX.Grid.Style, // FMX ListView FMX.ListView, FMX.ListView.Appearances, FMX.ListView.Types, FMX.ListView.Adapters.Base, // FMX ListBox y otros FMX.TabControl, FMX.ListBox, FMX.Edit, FMX.Memo, FMX.Memo.Types, FMX.ScrollBox, // REST components REST.Client, REST.Types, REST.Response.Adapter, // Additional FMX.ActnList, FMX.ImgList, FMX.Gestures; |