UPDATE: Nuovo blog post con la versione XE7
Introduzione
Esistono online diversi articoli che parlano di come integrare la lettura di codici a barre in applicazioni mobile Delphi (trovate dei link anche nel seguito di questo post).
Vedremo ora un approccio che permette di aggiungere le funzionalità di scansione e produzione di codici a barre (e/o QR code) in una app Delphi (Android), senza necessità di installare componenti di terze parti o compilare nella vostra app librerie di terzi.
Tutto si basa sull'interazione, mediata dal sistema operativo, con una applicazione già esistente (molto diffusa, gratuita e disponibile su Google Play Store) che si chiama Barcode Scanner (dettagli sulla licenza qui).
A costo di un certo livello di dipendenza da questa applicazione di terzi, il beneficio è di poterla sfruttare al massimo con poco sforzo, realizzando facilmente funzionalità che potrebbero essere sufficienti in un buon numero di situazioni.
Pro e contro
Vi anticipo subito quali sono i pregi/difetti di questo approccio:
Contro
- Si genera una dipendenza con Barcode Scanner (anche se solo per quanto riguarda le funzionalità di lettura/produzione di barcode, non per l'esecuzione in sè della vostra app): i vostri utenti dovranno installare sui loro dispositivi anche questa applicazione (attualmente gratuita);
- La funzionalità di lettura (scan) dei barcode si basa sull'assunto che Barcode Scanner di chiuda dopo aver scansionato un codice e copi il codice letto nella clipboard del dispositivo; se future versioni dell'applicazione dovessero comportarsi diversamente, la vostra applicazione potrebbe non funzionare;
- questa soluzione non è crossplatform (è valida solo per Android)
Pro
- Tutta la complessità di implementazione viene delegata a Barcode Scanner (incluso il supporto a numerosi formati di codici a barre e la gestione della camera);
- Non è necessaria alcuna libreria di terze parti da compilare con la vostra applicazione;
- Sia la lettura che la produzione di codici a barre si risolve con una manciata di righe di codice!
Altri approcci e link di riferimento
- Articolo di Fernando Rizzatto (iOS, ZBar SDK)
- Video di Jim McKeeth (iOS + Android)
- Articolo di Pawel Glowacki sulla gestione degli eventi delle applicazioni
Intent
Un Intent è sostanzialmente una descrizione di una operazione da compiere. Le applicazioni Android (activity) possono interagire attraverso gli intent (chiamandoli e registrandosi per servirli) un po' come le applicazioni Win32/64 possono interagire tramite ShellExecute o CreateProcess.
In Delphi sono presenti delle unit che ci aiutano a maneggiare direttamente i wrapper delle classi Java interessate e che ci permettono di effettuare le necessarie conversioni di tipo fra i tipi di dato Delphi e quelli Java. In particolare sono tre le unit che ci serviranno:
- Androidapi.JNI.GraphicsContentViewText, dove è definito il wrapper JIntent;
- Androidapi.JNI.JavaTypes, dove troviamo SharedActivity (una JActivity);
- FMX.Helpers.Android, che offre funzioni di utilità come StringToJString.
Una volta incluse queste unit, siamo in condizione di dichiarare ed eseguire un intent.
Come potete vedere in questo file Java di Barcode Scanner, essa espone diversi intent fra cui uno per avviare una scansione di un codice a barre e uno per produrre un codice a barre partendo da un contenuto testuale fornito nei parametri dell'intent.
Primo step: produzione di un codice a barre
Con queste poche righe di codice possiamo preparare ed eseguire l'intent ENCODE di Barcode Scanner:
procedure TForm1.ButtonProduceClick(Sender: TObject); var Intent: JIntent; begin Intent := TJIntent.JavaClass.init(StringToJString('com.google.zxing.client.android.ENCODE')); Intent.setPackage(StringToJString('com.google.zxing.client.android')); Intent.putExtra(StringToJString('ENCODE_TYPE'), StringToJString('TEXT_TYPE')); Intent.putExtra(StringToJString('ENCODE_FORMAT'), StringToJString(ComboBoxFormat.Items[ComboboxFormat.ItemIndex])); Intent.putExtra(StringToJString('ENCODE_DATA'), StringToJString(Memo1.Lines.Text)); SharedActivityContext.startActivity(Intent); end;
- Il valore ENCODE_DATA rappresenta il contenuto che vogliamo rendere con il barcode (ed è il contenuto di un memo presente sulla form della nostra applicazione);
- ENCODE_FORMAT rappresenta il tipo di codice a barre che vogliamo produrre (nel nostro esempio è preso da un combobox sulla form). I formati supportati attualmente sono i seguenti:
"UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128", "ITF", "RSS_14", "RSS_EXPANDED", "QR_CODE", "DATA_MATRIX".
Il risultato è che una volta lanciata l'activity descritta dal nostro intent, l'applicazione Barcode Scanner verrà eseguita e riceverà i parametri che abbiamo impostato nell'intent, producendo per noi un barcode del tipo specificato e con il contenuto indicato.
Sfruttando un secondo intent disponibile, possiamo chiedere a Barcode Scanner di avviarsi in modalità SCAN, presentando all'utente la tipica interfaccia grafica per individuare con la camera un codice a barre e scansionarlo. Una volta acquisito il codice, l'applicazione emette un beep acustico e si chiude, dopo aver copiato negli appunti il valore scansionato. Questo fa sì che la nostra applicazione torni in primo piano.
Il codice per eseguire l'intent SCAN è il seguente:
var Intent: JIntent; begin // empty the clipboard (to be sure not to match previous results) FClipBoardService.SetClipboard(''); WaitingForResults := True; // launch Barcode Scanner in SCAN mode Intent := TJIntent.JavaClass.init(StringToJString('com.google.zxing.client.android.SCAN')); Intent.setPackage(StringToJString('com.google.zxing.client.android')); Intent.putExtra(StringToJString('SCAN_MODE'), StringToJString('ONE_D_MODE,QR_CODE_MODE,PRODUCT_MODE,DATA_MATRIX_MODE')); SharedActivityContext.startActivity(Intent); end;
In teoria, il modo corretto di intercettare la fine dell'attività di scansione sarebbe implementare un gestore per onActivityResult ma questo comporta attualmente alcune complicazioni tecniche.
Usiamo quindi un semplice workaround: intercettiamo l'evento che ci segnala che la nostra applicazione è di nuovo in primo piano (ottimo articolo di Pawel Glowacki) e leggiamo il contenuto degli appunti per catturare il testo corrispondente al barcode letto da Barcode Scanner.
Codice per agganciare gli eventi dell'applicazione e per ottenere un riferimento alla clipboard:
procedure TForm1.FormCreate(Sender: TObject); begin FWaitingForResults := False; if not TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, IInterface(FClipBoardService)) then raise Exception.Create('Cannot get access to clipboard service!'); if not TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(FApplicationEventService)) then raise Exception.Create('Cannot get application event service'); FApplicationEventService.SetApplicationEventHandler(ApplicationEventHandler); end;
Codice per l'event-handler dei cambi di stato dell'applicazione:
function TForm1.ApplicationEventHandler(AAppEvent: TApplicationEvent; AContext: TObject): Boolean; var LClipboardContent: string; LFound: Boolean; begin case AAppEvent of aeBecameActive: begin if WaitingForResults then begin WaitingForResults := False; LClipboardContent := FClipBoardService.GetClipboard.AsString; LFound := LClipboardContent <> ''; if LFound then Log('Scan successful: ' + LClipboardContent) else Log('Scan failed (please retry)'); end; end; end; Result := True; end;
La procedura Log non fa altro che scrivere nel Memo della form principale.
Conclusioni e materiale
Questo è tutto. Come vedete si tratta di poche righe di codice e il meccanismo è semplice ma efficace.Se le vostre esigenze di leggere e produrre barcode non sono particolarmente eccessive, un approccio come quello illustrato dovrebbe essere una soluzione sufficiente nella maggioranza dei casi.
Link: Codice sorgente Delphi XE5
Link: Demo APK (da installare sul vostro dispositivo Android)
UPDATE: Nuovo blog post con la versione XE7
Buon lavoro e a presto!
Andrea