mercoledì 23 ottobre 2013

ITDevCon 2013

La quinta edizione della conferenza ITDevCon (European Delphi Conference), organizzata da BitTime, si tiene anche quest'anno a Verona, il 14 e 15 Novembre 2013.

La conferenza propone molti speech interessanti, suddivisi in tre sessioni parallele, qui trovate l'agenda, giorno per giorno:
Come sottolineato anche da Primož Gabrijelčič, non sarà facile scegliere fra le sessioni proposte! Al momento questa è la mia selezione (fra parentesi le scelte obbligate):
  • 14 Novembre
    • (Opening Keynote - Fabrizio Bitti e Daniele Teti) 
    • (XE5: The First Delphi... for Android - Marco Cantù)
    • (RaspberryPi: alta tecnologia a basso costo - Andrea Magni)
    • Delphi REST Client Library - Marco Cantù
    • (Firemonkey e gli stili: hands on - Andrea Magni)
    • DelphiMVCFramework: RESTful interfaces and dynamic web pages with Delphi and WebBroker - Daniele Teti
  • 15 Novembre
    • Delphi Toolbox: la cassetta degli attrezzi ideale per le applicazioni moderne - Marco Breveglieri
    • Messaging e notifiche asincrone per mobile - Marco Mottadelli
    • (Going to mobile with Delphi: best practices, problems and solutions - Daniele Spinetti e Daniele Teti)
    • The amazing Leap is not for games only... - Detlef Overbeek
    • From Zero to Hero in 60 minutes - Creating a client/server mobile application in one hour - Primož Gabrijelčič
    • Profiling techniques for Delphi - André Mussche
Ho ancora qualche incertezza ma c'è ancora tempo :-)

Le mie due sessioni (entrambe il 14 Novembre) sono abbastanza differenti fra loro: 
  • la prima è incentrata sul RaspberryPi e su un possibile approccio per sfruttarne le potenzialità riutilizzando le conoscenze di un programmatore Pascal e alcuni progetti opensource molto diffusi (lighttpd, Firebird, ...);
  • la seconda è dedicata a Delphi e soprattutto agli stili FireMonkey, con degli esempi pratici (anche di creazione di un componente visuale custom).
Ci vediamo a Verona!


lunedì 21 ottobre 2013

La "nuova" sfida: un mondo mobile (ma non solo)

Osservando i risultati di un recente sondaggio di Dimensional Research (commissionato da Embarcadero Technologies) si trova un riscontro oggettivo della crescente richiesta di applicazioni mobile che gli sviluppatori si trovano a fronteggiare.



Il sondaggio, svolto nell'agosto 2013, era specificamente rivolto a sviluppatori con esperienza e responsabilità in ambito Windows desktop ed ha raggiunto un campione di circa 1300 soggetti in tutto il mondo (53% EMEA, 25% America, 22% Asia Pacific). Vediamo insieme alcuni punti fondamentali emersi dagli sviluppatori intervistati e, sulla base di questi dati, cerchiamo di fare qualche considerazione.

Il mondo è mobile

Una larghissima fetta degli intervistati (85% circa) ha ricevuto la richiesta di sviluppare applicazioni per il mondo mobile. 
Una percentuale così alta indica che non è più ragionevole, per chi vuole essere competitivo sul mercato, ignorare l'esigenza di annoverare fra le proprie competenze lo sviluppo di applicazioni mobile: i clienti lo chiedono con sempre maggiore insistenza e aumenta il rischio che una mancata risposta comporti che si rivolgano ad altri concorrenti, con i rischi del caso.

Le aspettative dell'utente sono elevate

L'86% degli intervistati dichiara che le aspettative dei propri utenti rispetto alle applicazioni mobile da sviluppare sono elevate. 
Ciò significa che l'utente/committente si aspetta di avere dallo sviluppatore un'applicazione bella, veloce e affidabile... Generalmente, il raffronto viene fatto considerando le applicazioni esistenti più diffuse (GMail, FaceBook, Twitter, ecc.). Se da un certo punto di vista è positivo che i nostri utenti/committenti abbiano aspettative elevate, dal punto di vista di chi deve realizzare queste applicazioni significa confrontarsi con prodotti realizzati da team di sviluppatori dedicati, con elevate competenze specifiche, un budget (economico e di tempo di sviluppo) ben proporzionato e che magari hanno avuto il tempo di maturare (non sono certo alla prima versione!).
Certamente una sfida stimolante che però non sempre è supportata da una comprensione, da parte dell'utente/committente, delle difficoltà tecniche, delle competenze da acquisire e dei costi (sia economici che di tempo) che lo sviluppatore deve assorbire per raggiungere l'obiettivo.

Non si tratta di una transizione "vecchio per nuovo"

Il 99% degli intervistati sostiene che dovranno continuare a supportare le attuali applicazioni desktop (e il 95% sostiene che dovranno anche continuarne lo sviluppo).
Come dicevamo, il fardello di cui lo sviluppatore deve farsi carico, in termini di competenze e difficoltà tecniche, per supportare la nuova classe di dispositivi mobile, non è leggero. Purtroppo, non si può nemmeno dire che le risorse necessarie ad affrontare la nuova sfida possano in qualche modo essere recuperate da altri fronti su cui lo sviluppatore è già impegnato: nonostante il forte cambiamento del mercato a favore dei dispositivi mobile, non è ancora il momento (se mai arriverà) per dismettere lo sviluppo desktop.

In particolare, spesso (65%) viene richiesto di "spostare" alcune funzionalità di un'applicazione esistente in modo che siano fruibili su un dispositivo mobile oppure (43%) di replicare le stesse funzionalità dell'applicazione desktop su un dispositivo mobile (aggiungendo piattaforme senza però dismettere quelle esistenti).
Anche se è presente una forte (58%) richiesta anche di applicazioni mobile "nuove", spesso si tratta di ampliare la compatibilità delle proprie applicazioni rendendole fruibili (in toto o in parte) su nuove piattaforme, trasformando di fatto quella che potrebbe sembrare una transizione netta (butto il desktop, benvenuto tablet o smartphone) in una questione più delicata che introduce il problema di dover poi supportare in ogni fase (analisi, sviluppo, test e deploy) tutte le piattaforme supportate.

Tanti, nuovi e accattivanti ma limitati, a chi importa?

Fra le righe dei dati riportati nei risultati del sondaggio, si intravede quello che è un altro problema di cui non sempre l'utente/committente sembra accorgersi: i nuovi dispositivi, per quanto belli e tecnologicamente sofisticati, hanno alcune limitazioni che possono rivelarsi problematiche per chi deve perseguire l'obiettivo di dare un'esperienza utente di qualità.

Ad esempio, i nuovi dispositivi non sempre hanno capacità di elaborazione comparabili con quelle di un computer "tradizionale" (e lo stesso vale per le capacità di archiviazione disponibili).
Inoltre, il contesto in cui questi dispositivi vengono usati, appunto, mobile, introduce anche limitazioni riguardanti la quantità di informazioni fruibili sul display (considerate anche le caratteristiche di un sistema di input basato su touch e multi-touch) e la connessione dati (in termini di disponibilità, di velocità e di qualità).

La grande varietà di questi dispositivi, che per l'utente/committente spesso viene appiattita in termini di due grandi categorie indistinte (smartphone/tablet), risulta essere un'ulteriore complicazione per lo sviluppatore, cui viene chiesto di garantire non solo un generico funzionamento ma anche un buon livello qualitativo della user-experience fruibile, in ogni caso.

Dato questo scenario è abbastanza comprensibile la preoccupazione degli sviluppatori che, tradotta in cifre nel sondaggio, dichiarano che i propri utenti:
  • si aspettano di avere applicazioni complesse che siano però altrettanto semplici da usare di quelle di base (54%)
  • non considerano le limitazioni dei dispositivi mobile, pensando che possano erogare tutte le funzionalità presenti nel mondo desktop (51%)
  • richiedono che l'applicazione sia disponibile per tutte le piattaforme esistenti (45%)
  • si aspettano delle prestazioni eccessive (28%)

Conclusioni

  1. I dati del sondaggio evidenziano che per moltissimi sviluppatori Windows desktop è giunto il momento di offrire ai propri utenti/committenti delle applicazioni mobile di qualità;
  2. molti di questi sviluppatori sono giustamente intimoriti (o spiazzati) dal numero e dal livello richiesto di competenze tecniche necessarie per fornire un'esperienza all'altezza delle aspettative;
  3. la presenza di diverse piattaforme e di diverse classi di dispositivi mobile da coprire, moltiplica gli sforzi richiesti;
  4. viene comunque richiesto di continuare a supportare attivamente anche le piattaforme desktop, per cui si stanno aggiungendo nuovi fronti, non si tratta di uno scambio;
  5. (parziale conseguenza del punto precedente) molti sviluppatori (95%) riconoscono il valore di avere una soluzione (ambiente di sviluppo) che permetta di avere un approccio unico per lo sviluppo di applicazioni desktop e mobile.
Indubbiamente, un ambiente di sviluppo che permetta di creare applicazioni desktop e mobile che offra agli sviluppatori l'opportunità di avere una singola base di codice da compilare per le principali piattaforme esistenti (Windows, Mac OSX, iOS e Android), potrebbe avere una marcia in più!
Se poi doveste essere già affezionati utilizzatori di uno degli strumenti di sviluppo più produttivi di sempre, non vi resta che procurarvi la versione XE5 e farci un giro :-)

lunedì 14 ottobre 2013

Una form di attesa per operazioni lunghe in una mobile application (iOS/Android)

Introduzione

Un fattore fondamentale del successo delle nostre applicazioni mobile è la loro fluidità: molti utenti "pretendono" che le nostre applicazioni siano sempre reattive e non ci siano momenti di "freeze" che vengono immediatamente classificati come malfunzionamenti.

Nello sviluppo di una applicazione mobile, può capitare di dover effettuare delle operazioni lunghe in termini di tempo che possono pregiudicare la responsività della nostra interfaccia utente. Il problema non è certo nuovo e anche in applicazioni tradizionali (desktop) per risolverlo si può ricorrere alla programmazione multithread, eseguendo l'operazione in background e lasciando il thread principale della nostra applicazione libero di rispondere alle esigenze dell'utente.

Vediamo come, in una applicazione FireMonkey mobile, si può realizzare una form di attesa generica, utile per dare l'impressione al nostro utente che l'applicazione sia effettivamente "viva" e allo stesso tempo fornire alcuni dettagli sull'operazione in corso.

Prenderò come esempio di operazione lunga una GET HTTP, caso abbastanza comune e caratterizzato da alcune caratteristiche peculiari:
  • si tratta di un'operazione bloccante (cioè il flusso di esecuzione è interrotto dall'inizio alla fine dell'operazione);
  • può richiedere un tempo variabile (a seconda della connettività e della risorsa HTTP richiesta);
  • sono disponibili dei dettagli sullo stato di avanzamento dell'operazione (es. bytes scaricati).
Altri esempi di operazioni del tutto analoghe sono:
  • l'esecuzione di operazioni su un database locale;
  • l'apertura di un dataset remoto;
  • la fruizione di servizi remoti (REST, SOAP, XML-RPC ecc);
  • operazioni di calcolo complesse;
  • operazioni di rendering grafico;
  • qualunque operazione che abbia tempi di attesa di più di qualche decimo di secondo.
L'esempio è realizzato con Delphi XE5, su una applicazione FireMonkey mobile, funziona sia per iOS che per Android (in realtà funzionerebbe, usando una form Firemonkey non-mobile, anche sulle altre piattaforme, come Windows e MacOSX) e usa il componente TIdHTTP della suite Indy.
Inoltre, l'implementazione proposta prevede l'uso dei metodi anonimi, una caratteristica del linguaggio, introdotta con Delphi 2009, che permette in modo semplice di delegare l'esecuzione di codice custom (quello specifico dell'operazione che si vuole eseguire, per esempio) all'interno di un contesto più generico (il codice che mostra e gestisce la form di attesa).

1) Eseguire una operazione bloccante

Supponiamo di avere creato una nuova applicazione FireMonkey mobile e avere un componente TIdHTTP da usare per eseguire la GET HTTP e salvare la risposta del server in un TMemoryStream:

var
  LContent: TMemoryStream;
  LDetails: string;

// ...
  LContent := TMemoryStream.Create;
  try
    IdHttp1.Get('http://www.adomain.com/test/large_image.png', LContent);
    LDetails := LContent.Size.ToString + ' bytes';
  finally
    LContent.Free;
  end;

Se eseguiamo questo pezzo di codice in un evento OnClick di un TButton (se l'immagine richiesta è di una certa dimensione e magari la connettività non è particolarmente brillante), quello che succederà quando l'utente clicca sul pulsante è il congelamento dell'interfaccia utente fino al completamento del download. Questo capita perchè l'operazione viene effettuata nello stesso thread di esecuzione che si occupa di gestire l'interfaccia utente dell'intera applicazione.

2) Otteniamo i dettagli di avanzamento dell'operazione

Il componente TIdHTTP ha alcuni eventi disponibili per tracciare l'esecuzione delle operazioni di GET. In particolare si vedano OnWorkBegin, OnWorkEnd e OnWork:
  • il primo permette di sapere che l'operazione è stata avviata e come parametro (AWorkCountMax) offre l'indicazione della dimensione totale del file da scaricare;
  • OnWorkEnd è, come è facile intuire, la notifica di operazione completata (non ci interessa particolarmente);
  • OnWork viene chiamato periodicamente durante l'operazione e ha un parametro AWorkCount che ci permette di sapere a che punto siamo con il download del file.
Potremmo scrivere dei gestori di evento per stampare su un TMemo le informazioni disponibili ma questo non cambierà il fatto che l'interfaccia utente sembrerà comunque congelata durante l'operazione e dovremo cercare un modo esplicito per forzare il ridisegno del componente TMemo una volta aggiunto del testo.


3) Prepariamo una form di attesa da mostrare all'utente

Posto che l'operazione che dobbiamo eseguire richiede un certo tempo, vogliamo mostrare all'utente una form di attesa, magari con una animazione che indichi che l'operazione è in corso, in modo da fugare ogni dubbio riguardante malfunzionamenti o "blocchi", che potrebbero portare il nostro utente a terminare brutalmente l'applicazione.

Non serve molto:
  • una TLabel (TitleLabel) mostrerà un titolo nella parte superiore della form;
  • un TLayout (ProgressLayout) ospiterà delle informazioni sullo stato di avanzamento dell'operazione (usando una TProgressBar e un'altra TLabel), nella parte inferiore della form;
  • un TLayout (ImageLayout) ospiterà una TImage con della grafica (che potete personalizzare a piacimento, io ho usato l'immagine di una rotella da ingranaggio).

4) Incastoniamo l'esecuzione dell'operazione durante la visualizzazione della form

In linea teorica quello che vorremmo ottenere è:
  • lanciare l'operazione lunga;
  • mostrare la form di attesa "viva" (cioè animata e comunque responsiva ad eventuali azioni dell'utente, come una richiesta di annullamento dell'operazione stessa);
  • alla conclusione dell'operazione, chiudere la form di attesa e magari eseguire qualcosa con i dati scaricati.
Cerchiamo di impacchettare il codice dell'operazione in un metodo anonimo per passarlo "come se fosse una variabile" alla form di wait che lo eseguirà al momento opportuno (cioè dopo essersi mostrata all'utente e aver avviato l'animazione):

procedure
var
  LContent: TMemoryStream;
begin
  LContent := TMemoryStream.Create;
  try
    IdHttp1.Get(eHTTPGetURL.Text, LContent);
  finally
    LContent.Free;
  end;
end);

eHTTPGetURL è un TEdit posto sulla form della mia applicazione, dove l'utente potrebbe specificare la URL della risorsa da scaricare.

Prepariamo un metodo nella form di attesa, che riceva questo metodo anonimo che rappresenta il Task da eseuire, insieme ad altri due metodi anonimi che rappresentano il codice da eseguire al completamento dell'esecuzione del task e quello da eseguire in caso avvengano delle eccezioni durante l'esecuzione dello stesso:

procedure TWaitForm.Run(ATask, AOnComplete: TProc(twaitform);
  AOnError: TProc(twaitform, exception));
begin
  Show;
  WaitAnimation.Start;

  TThread.CreateAnonymousThread(
    procedure
    begin
      try
        FHadErrors := False;
        ATask(Self);
      except on E:Exception do
        DoErrorHandling(E, AOnError);
      end;

      DoOnCompleted(AOnComplete);
    end).Start;
end;

Il codice è breve ma presenta alcune caratteristiche degne di nota:
  • i parametri ATask, AOnComplete e AOnError sono dei riferimenti a metodi anonimi con un certo numero e tipo di parametri. La sintassi utilizzata è quella dei generics (altro potente meccanismo introdotto nelle ultime versioni di Delphi e presente il molti altri linguaggi di programmazione), con le parentesi angolari: ATask e AOnComplete sono due procedure che hanno come parametro un'istanza di TWaitForm.
    L'idea è quella di passare al metodo Run le tre cose da fare (esecuzione task, esecuzione codice dopo completamento del task e esecuzione codice in caso di errori), in modo che possa comporle nel modo più appropriato;
  • l'esecuzione di ATask è protetta da un blocco try..except che garantisce l'esecuzione di AOnError in caso di errori (e trappa l'eccezione, in modo che non affiori all'esterno);
  • la form ha un field FHadErrors che tiene traccia del presentarsi di eventuali eccezioni (utile per esempio nel codice di AOnComplete);
  • l'esecuzione del task, della gestione delle eccezioni e delle operazioni post-conclusione del task vengono effettuate in un thread separato, grazie all'utilizzo di TThread.CreateAnonymousThread, che permette di eseguire un metodo anonimo in un nuovo thread, in background;
Come vedete, si tratta di poche righe di codice che permettono di fare però una cosa abbastanza complessa:
  • eseguire un pezzo di codice variabile (ATask) in un thread secondario;
  • avere una chance di agganciare altro codice variabile (AOnError e AOnComplete) a due eventi principali di interesse: i casi di errore e la notifica di completamento di ATask;
  • avere a disposizione in questi pezzi di codice variabili (metodi anonimi) il riferimento alla form di attesa specifica, che può esporre alcuni metodi utili per la modifica del contenuto a video (es. stato di avanzamento), avendo l'opportunità di nascondere al programmatore la necessità di sincronizzare l'esecuzione fra thread diversi.
A proposito di questa ultima necessità, si vedano i metodi (di TWaitForm):
procedure SetupProgress(const AProgress, AMin, AMax: Single; const AMessage: string = '');

procedure UpdateProgress(const AProgress: Single; const AMessage: string = '');

la cui implementazione è thread-safe e che possono quindi essere invocati sia all'interno dei metodi anonimi ATask, AOnError e AOnCompleted che da codice nel thread principale dell'applicazione o in altri thread.
Ad esempio, riporto l'implementazione di UpdateProgress che fa uso di TThread.Synchronize per eseguire il codice che manipola gli elementi della GUI in modo safe rispetto al thread principale di esecuzione:

procedure TWaitForm.UpdateProgress(const AProgress: Single;
  const AMessage: string);
begin
  TThread.Synchronize(
    TThread.CurrentThread,
    procedure
    begin
      ProgressBar.Value := AProgress;
      ProgressLabel.Text := AMessage;
      if AMessage = '%' then
        ProgressLabel.Text := Format('%.1f', [100*(ProgressBar.Value / ProgressBar.Max)]) + '%';

      ProgressBar.Visible := AProgress > 0;
      ProgressLabel.Visible := AMessage <> '';
    end);
end;

Questo metodo, per esempio, può essere chiamato anche nel gestore di OnWork del nostro componente TIdHTTP, per aggiornare la barra di avanzamento presente sulla TWaitForm e visibile all'utente durante il download.

5) Esempi di utilizzo

Se alcuni passaggi esposti finora vi sembrano un po' complicati o poco chiari, non dovete preoccuparvi (al netto magari di voler approfondire qualche argomento :-) ) perchè in realtà il codice necessario per utilizzare la form è molto più semplice, ad esempio potreste avere un button con il seguente handler di OnClick:

var
  LDetails: string;
begin
  FWaitForm := TWaitForm.CreateAndRun(
    'Long operation: HTTP GET',  // titolo

    procedure(AWaitForm: TWaitForm)  // ATask
    var
      LContent: TMemoryStream;
    begin
      LDetails := '';
      LContent := TMemoryStream.Create;
      try
        IdHttp1.Get(eHTTPGetURL.Text, LContent);
        LDetails := LContent.Size.ToString + ' bytes';
      finally
        LContent.Free;
      end;
    end,

    procedure(AWaitForm: TWaitForm) // AOnCompleted
    begin
      if not AWaitForm.HadErrors then
        Memo1.Lines.Add('Completed: ' + LDetails);
    end,

    procedure (AWaitForm: TWaitForm; AException: Exception)  // AOnError
    begin
      Memo1.Lines.Add('Error: ' + AException.Message);
    end
  );
end;

Nei gestori di evento del componente TIdHTTP, grazie al fatto che ci salviamo il riferimento alla TWaitForm in FWaitForm, possiamo scrivere semplicemente:

procedure TForm1.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCount: Int64);
begin
  FWaitForm.UpdateProgress(AWorkCount, '%');
end;

procedure TForm1.IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCountMax: Int64);
begin
  FWaitForm.SetupProgress(0, 0, AWorkCountMax, 'Downloading...');
end;

Il risultato è mostrato (senza animazione ovviamente :-) ) di seguito. Il primo screenshot mostra l'applicazione in esecuzione nel simulatore iOS, mentre il secondo è preso direttamente dal mio smartphone Android:


Conclusioni

Abbiamo realizzato una form di attesa che permette di eseguire una qualunque operazione lunga in un thread secondario, lasciando fluire l'esecuzione del thread principale dell'applicazione a garanzia della responsività della nostra interfaccia (e non solo) senza perdere la possibilità di eseguire del codice al completamento dell'operazione richiesta e quella di gestire eventuali eccezioni.

Grazie all'uso dei metodi anonimi e di alcune funzionalità del linguaggio introdotte nelle ultime versioni di Delphi, lo sviluppatore che utilizza questa form non deve avere particolari conoscenze della programmazione multithread e l'utilizzo risulta semplice ed intuitivo nella maggior parte dei casi.

Materiale

Potete scaricare il codice sorgente completo dell'esempio al seguente link:
Source code - Link to DropBox

Se volete provarla direttamente sul vostro dispositivo Android potete scaricare l'APK al seguente link: