lunedì 24 marzo 2014

Leggere e produrre Barcode con Delphi XE5 (Android)

UPDATENuovo 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

  1. 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);
  2. 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;
  3. questa soluzione non è crossplatform (è valida solo per Android)

Pro

  1. 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);
  2.  Non è necessaria alcuna libreria di terze parti da compilare con la vostra applicazione;
  3. 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

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:

  1. Androidapi.JNI.GraphicsContentViewText, dove è definito il wrapper JIntent;
  2. Androidapi.JNI.JavaTypes, dove troviamo SharedActivity (una JActivity);
  3. 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.



Secondo step: lettura di un codice a barre

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

4 commenti:

  1. Salve Andrea,
    qualche altra soluzione senza usare il Barcode Scanner? qualche cosa diretta?
    Saluti
    Bleri

    RispondiElimina
    Risposte
    1. Nella sezione "Altri approcci" di questo blog post, ci sono link al lavoro di Fernando Rizzato e al video di Jim McKeeth in cui si usa la libreria ZXing compilandola direttamente nella app Delphi.
      Così non si hanno dipendenze da applicazioni di terze parti come Barcode Scanner.

      Buon lavoro e a presto,
      Andrea

      Elimina
  2. Grazie Andrea
    Ottimo Articolo e il sorgente funziona sia su XE6 che XE7.

    RispondiElimina
  3. I would like to use the lib https://sourceforge.net/p/zbar/news/2012/03/zbar-android-sdk-version-01-released/ with delphi XE5, but the lib and. Jar and. So, You can use it with delphi XE5, how do? I need to read the barcode on my webcam!

    RispondiElimina