Objective-C 2.0 – Direttive del compilatore

learn-objective-cSono alle prese con lo sviluppo di un’app per iPhone e siccome mi trovo a dover utilizzare, per forza di cose, le direttive messe a disposizione dal linguaggio, ho pensato che può essere utile avere una lista che le elenca e che ne spiega il significato. Ho trovato anche un post in rete molto interessante che in pratica fa la stessa cosa, e quindi ne approfitto per diffondere anche io nel panorama italiano.

Let’s go !

@class

Dichiara una classe senza dover utilizzare l’istruzione import con il relativo file header. Viene utilizzato per le class forward declaration.

@class DevMeClass;

@protocol @required @optional @end

Definisce l’inizio della dichiarazione di un protocol. Opzionalmente può dichiarare altri protocol ai quali è conforme come segue:

@protocol DevMeProtocol

 

  • @required: dichiara i metodi come metodi required.
  • @optional: dichiara i metodi come metodi optional. La classe che implementa il protocollo può decidere se implementare o no il metodo opzionale. Le classi che fanno uso del protocollo devono verificare che il metodo opzionale esista prima di utilizzarlo.
    [object respondsToSelector:@selector(optionalProtocolMethod)];
  • @end: dichiara la fine del protocollo.

@interface @public @package @protected @private @property @end

Definisce l’inizio di una classe o category.

Dichiarazione di una classe.

Le classi in Objective-C devono derivare direttamente o indirettamente dalla classe NSObject. La direttiva @interface per la dichiarazione di una classe può opzionalmente dichiarare che la classe è conforme ad altri protocolli.

@interface DevMeClass : DevMeSuperClass <DMProtocol, anotherDMProtocol> {
  // instance variables can be
@public
@package
@protected
@private
}
 
// property declarations
@property (atomic, readwrite, assign) id aProperty;
 
// public instance and/or class method declarations
@end

Dichiarazione di una category

La direttiva @interface non può aggiungere variabili d’istanza ma può opzionalmente dichiarare la conformità ad altri protocolli.

@interface DevMeClass (CategoryName) <DMProtocol, anotherDMProtocol>
 
// property declarations
@property (atomic, readwrite, assign) id dmProperty;
 
// method declarations
@end

Direttive

  • @public: dichiara la variabile d’istanza pubblica. Le varibili pubbliche possono essere lette o modificate con la notazione punto
    devme->bestBlog = true;
  • @package: dichiara la variabile d’istanza pubblica all’interno del framework che definisce la classe ma privata all’esterno.Funziona sui sistemi a 64 bit, su quelli a 32 bit si comporta esattamente come @public.
  • @protected: dichiara la variabile d’istanza protetta e quindi accessibile solo dalla classe che la definisce e dalle sue eventuali derivate.
  • @private: dichiara la variabile d’istanza privata e quindi accessibile solo dalla classe che la definisce.
  • @property: dichiara una proprietà la quale può essere accessibile con la notazione punto. Può essere eseguita dalle specialio parole chiave che ne definiscono l’esatto comportamento. Possono essere:
    • readwrite (default), readonly). Genera il getter e il setter (readwrite), solo il getter ((readonly).
    • assign (default), retain, copy. Si applica solo alle proprietà per cui è possibile effetuare un cast sul tipo id. assign, assegna il valore passato; retain, invia un release alla variabile d’istanza, invia retain al nuovo oggetto e assegna l’oggetto retained alla variabile d’istanza; copy invia un release alla varibile d’istanza, invia copy al nuovo oggetto e assegna l’oggetto copiato alla variabile d’istanza. Negli ultimi casi si è responsabili della deallocazione dell’oggetto.
    • atomic(default), non atomic. Le proprietà atomic sono thread-safe mentre le non-atomic possono essere accedute simultaneamente. Le non atomic sono pià veloci delle atomic e spesso usate nelle applicazione single thread.
    • weak (default), strong. Sono disponibili se la funzionalità ARC (automatic reference counting) è abilitata. La chiave strong è sinonimo di retain, mentre weak è sinonimo di assign.
  • @end: dichiara la fine della dichiarazione dell’interfaccia.

 

@throw @try @catch @finally

Vengono utilizzate per la gestione delle eccezioni.

@try {
    // code that might throw an exception &hellip; like this one:
    NSException *exception = 
        [NSException exceptionWithName:@"DevMeException"
                                reason:@"Test exception!"
                              userInfo:nil];
    @throw exception;
}
@catch (CustomException *ce) {
    // handling ...
}
@catch (NSException *ne) {
    // generic NSException handling ...
 
    // to simply re-throw the caught exception in a catch block:
    @throw;
}
@finally  {
    // after try or catch
}

@synchronized

Asscirua che il codice presente all’interno del blocco può essere disponibile solo ad un thread alla volta.

-(void) syncMethod:(id)object {
   @synchronized(object) {
      // code that works with locked object 
   }
}

@autoreleasepool

Nelle applicazioni in cui è abilitato ARC (Automatic Reference Counting) è necessario usare @autoreleasepool al posto di NSAutoreleasePool. Non bisognerebbe dichiarare una variabile all’interno di un blocco @autoreleasepool e continuare ad usarla dopo il blocco.

-(void) dmMethod {
    @autoreleasepool {
        // code that creates a large number of temporary objects
    }
}

@encode

Restituisce il carattere stringa della codifica di un tipo.

-(void) encMethod {
    char *enc1 = @encode(int);                 // enc1 = "i"
    char *enc2 = @encode(id);                  // enc2 = "@"
    char *enc3 = @encode(@selector(aMethod));  // enc3 = ":"
 
    // practical example:
    CGRect rect = CGRectMake(0, 0, 100, 100);
    NSValue *v = [NSValue value:&rect withObjCType:@encode(CGRect)];
}

@compatibility_alias

Consente di generare un alias per una classe. Il primo parametro è il nome dell’alias della classe, il quale può anche non esistere. Il secondo parametro è il nome della classe. a cui l’alias fa riferimento.

@compatibility_alias AliasClassName ExistingClassName

@string

Dichiara un oggetto NSString. Per tale oggetto non è necessario gestirne la memoria.

-(void) devmeMethod {
    NSString* str = @"Ciao questo &egrave; il blog di devme.";
    NSUInteger strLength = [@"Hello world!" length];
}

The end

Siamo giunti alla fine….per ora naturalmente. Sentitevi liberi di comunicare eventuali errori delle direttive. Nel frattempo, direttamente dal blog da cui ho tratto ispirazione, trovate la stessa lista in pdf ;) .

Stay tuned!

Chroot: come creare un ambiente a 32-bit su AMD 64

tuxMi è capitato pochi giorni fa di dover far fronte a questo problema: eseguire un software basato su perl 5.8.8 sparito ormai dalle più attuali distribuzioni ubuntu per essere precisi. La mia, che non è la ultimissima versione, la lucid, mette a disposizione il pacchetto perl 5.10.x.x, per ovviare avrei dovuto fare il downgrade del pacchetto perl, con tutti i problemi annessi e connessi. Per altro il SW è supportato solo su architetture a 32 bit, praticamente non avevo chance. Come fare a risolvere ? Semplice, creando un ambiente a 32 bit, attraverso il comando chroot. Vediamo come.

Dopo una breve ricerca, ho trovato che il pacchetto perl 5.8.8 è presente nella distro ubuntu hardy quindi ho battezzato quella versione come target del mio ambiente a 32 bit.

#1. Installare il pacchetto debootstrap il quale contiene una serie di script utili:

mulp@devme:~$ apt-get install debootstrap

Per saperne di più del comando vi rimando al seguente link

#2. Installare il pacchetto dchroot il quale contiene una serie di comandi utili alla creazione e all’accesso su diversi ambienti chroot:

mulp@devme:~$ apt-get install dchroot
mulp@devme:~$ mkdir /devme/chroot

Posizionatevi quindi nel punto del file system in cui desiderate creare fisicamente l’ambiente.

#3. Editare il file /etc/dchroot.conf e aggiungere la seguente riga:

hardy_i386 /devme/chroot/hardy_i386

Assicuratevi che il percorso sia corretto.

#4. Ci siamo. Siamo pronti a creare il nuovo ambiente. Eseguire il seguente comando:

mulp@devme:~$ debootstrap --variant=buildd --arch i386 hardy /devme/chroot/hardy_i386 http://archive.ubuntu.com/ubuntu/

Al termine dell’esecuzione avrete il nuovo sistema installato. Per ulteriori dettagli sul comando debootstrap vi rimando al man di sistema, aggiungo solo che il parametro variant indica il tipo di sistema che si vuol creare, in questo buildd. Il nuovo sistema creato avrà installati i pacchetti base-system.

#5. Procediamo ora alla configurazione del nuovo sistema. Eseguire i seguenti comandi:

mulp@devme:~$ cp /etc/resolv.conf /devme/chroot/hardy_i386/etc/resolv.conf
mulp@devme:~$ cp /etc/apt/sources.list /devme/chroot/hardy_i386/etc/apt/
mulp@devme:~$ chroot /devme/chroot/hardy_i386/
mulp@devme:~$ apt-get update
mulp@devme:~$ apt-get install gnupg locales dialog
mulp@devme:~$ apt-get update
mulp@devme:~$ locale-gen it_IT.UTF-8
mulp@devme:~$ tzconfig
mulp@devme:~$ exit

In sostanza si ereditano alcune configurazioni dell’ambiente principale all’interno di quello appena creato. Il primo comando consente di poter navigare dal nuovo ambiente e il secondo mette a disposizione le source list di apt affinché sia possibile aggiornarlo. Vengono installi alcuni comandi fondamentali e impostato il locale corretto. Ricordatevi di modificare il file sources.list se la distro del vostro sistema differisce dall’ambiente creato.

#6. A questo punto per accedere all’ambiente appena creato si utilizza il comando dchroot -d oppure dchroot -d- c hardy_i386 se avete a disposizione più ambienti. Vi accorgerete che il comando di cui sopra richiedono i diritti da superutente. Affinchè sia possibile accedere da utente normale, è necessario copiare alcuni file di configurazione dall’ambiente centrale in quello appena creato:

mulp@devme:~$ cp /etc/passwd /devme/chroot/hardy_i386/etc/
mulp@devme:~$ sed 's/\([^:]*\):[^:]*:/\1:*:/' /etc/shadow | tee /devme/chroot/hardy_i386/etc/shadow
mulp@devme:~$ cp /etc/group /devme/chroot/hardy_i386/etc/
mulp@devme:~$ cp /etc/hosts /devme/chroot/hardy_i386/etc/

Il comando sed rimuove le password criptate dal file shadow. Dal momento che stiamo facendo login in un’ambiente chroot non sono necessarie. In questo modo evitiamo anche di duplicare il file delle password su due ambienti diversi.

#7. Se si vuole poter eseguire il comando sudo è necessario copiare i seguenti file:

mulp@devme:~$ cp /etc/sudoers /devme/chroot/hardy_i386/etc/
mulp@devme:~$ chroot /devme/chroot/hardy_i386
mulp@devme:~$ dpkg-reconfigure passwd
mulp@devme:~$ passwd &lt;username&gt;
mulp@devme:~$ apt-get install sudo

E infine aggiungere la seguente riga nel file /etc/sudoers nell’ambiente chroot.

# Members of the admin group may gain root privileges
%mulp ALL=(ALL) ALL

#8. Procediamo con il mapping di alcune directory che sono utili all’interno dell’ambiente chroot. Editare il file /etc/fstab e aggiungere le seguenti righe:

/home         /devme/chroot/hardy_i386/home        none    bind      0 0
/tmp          /devme/chroot/hardy_i386/tmp         none    bind      0 0
/media/cdrom  /devme/chroot/hardy_i386/media/cdrom none    bind      0 0
/dev          /devme/chroot/hardy_i386/dev         none    bind      0 0 
proc-chroot   /devme/chroot/hardy_i386/proc        proc    defaults  0 0
devpts-chroot /devme/chroot/hardy_i386/dev/pts     devpts  defaults  0 0

Assicuratevi di effettuare la modifica sul file /etc/fstab dell’ambiente principale e che esistano tutti i mount point. Quindi eseguire il comando:

mulp@devme:~$ mount -a

#9. Infine, se volete che di fianco nel prompt appaia l’indicazione dell’ambiente chroot editate il file /devme/chroot/hardy_i386/etc/debian_chroot e aggiungete la seguente riga:

hardy_i386

Se il file non esiste createlo.

Stay tuned!

Android: Contacts API

androidA partire dalla versione 2.0 la piattaforma Android ha migliorato notevolmente le API per la gestione dei contatti, favorendo lo sviluppo di applicazioni che
gestiscono account provenienti da diverse fonti. L’idea principale è che per gestire differenti contatti provenienti da fonti diverse (gmail, telefono, sim, facebook, etc) 
viene utilizzata una funzione di aggregazione che in automatico riconosce i contatti simili, e li presenta come una singola entità.

La struttura dei Contatti su basa su 3 tabelle principali: contacts, raw contacts e data.

contacts-2

Data è una tabella generica che memorizza le informazioni legate ad un raw contact, nome, foto, indirizzo email, gruppo di appartenenza, numero di telefono, etc. Ciascuna riga è identificata da un MIME type che ne identifica il tipo.
Ad esempio, il tipo Phone.CONTENT_ITEM_TYPE indica il numero di telefono, mentre Email.CONTENT_ITEM_TYPE indica l’indirizzo email. E’ possibile estendere i tipi predefiniti estendendo la classe ContactsContract.CommonDataKinds.
Raw Contacts memorizza le informazioni dell’insieme di Data che descrivono le informazioni di una persona associate ad una singola sorgente di contatti. Si consideri ad esempio, una riga della tabella che memorizza informazioni relative ad un account google, facebook, etc. Contacts rappresenta l’aggregazione di uno o più raw contacts che descrivono la stessa persona, memorizzate all’interno di un singolo Contact.
In fase di visualizzazione dei contatti all’utente, un’applicazione dovrebbe agire a livello di Contacts poiché è l’entità che fornisce una visualizzazione aggregata dei contatti dalle varie fonti.

Inserimento di un numero di telefono

Per inserire un numero di telefono utilizzando le nuove API dei contatti è necessario ottenere l’ID del raw contact sul quale si vuole aggiungere l’informazione, quindi creare un nuova entry nella tabella Data:

package it.devme.contacts;
import android.provider.ContactsContract.CommonDataKinds.Phone;
...
    ContentValues values = new ContentValues();
    values.put(Phone.RAW_CONTACT_ID, idRowContact);
    values.put(Phone.NUMBER, mobileNumber);
    values.put(Phone.TYPE, Phone.TYPE_MOBILE);
    Uri uri = getContentResolver().insert(Phone.CONTENT_URI, values);
....

Aggregazione dei contatti

In fase di sincronizzazione dei contatti da diverse fonti, è possibile che più contatti identifichino la stessa persona pur con sottili differenze nei dati. Ad esempio, il caro Mario Rossi, potrebbe essere sia il nostro collega di lavoro che il nostro caro amico, in questo modo si avrebbero due contatti diversi con i relativi dati. Per evitare tali sovrapposizioni, il sistema rileva queste condizioni e aggrega i contatti in modo tale da combinarli in uno singolo, aggregato.

 

Aggregazione automatica

Non appena un raw contact viene aggiunto o modificato, il sistema cerca di rilevare eventuali sovrapposizioni con altri raw contact, in modo tale da cercare di aggregarli. Se non viene rilevato nessun contatto, allora verrà creato un’unico contatto contente i dati iniziali. Se viene trovato un contatto allora i dati di entrambi verranno aggregati in un solo contatto. Altrimenti nel caso di più match verranno scelti quelli ‘più vicini’ all’originale. In generale, il match tra due contatti avviene se viene soddisfatta una delle seguenti:

  • hanno lo stesso nome
  • hanno lo stesso nome ma in ordine differente (es. "Mario Rossi", "Rossi Mario")
  • uno dei due ha un diminuitivo dell’altro (es., "Roberto Verdi", "Rob Verdi")
  • uno di due ha solo il nome o cognome  e fa match con altri raw contact. Questa regola è poco affidabile e viene utilizzata assieme ad altre regole verificando anche il match rispetto ad altri dati, come il numero di telefono, email, nickname o altro. (es, Mario ["fulmine"] = Michela Bianchi ["fulmine"])
  • almeno uno dei due contatti non ha il nome ma condividono il numero di telefono, l’indirizzo email o il nickname. (es, Roberto Verdi [robin@devme.it] = robin@devme.it).

I confronti tra nomi non sono case-sensitive per evitare errori ovvi, così come i confronti tra numeri di telefono nei quali non vengono considerati caratteri come ‘+’, ‘#’ etc.

Aggregazione esplicita

In alcuni casi però potrebbe non essere richiesta l’aggregazione automatica dei contatti, per requisiti di una certa applicazione o altro. In questi casi si ricorre all’aggregazione esplicita, attraverso l’utilizzo delle API dei contatti è possibile specificare diversi modi di aggregazione dei dati e alcune eccezioni.

 

Aggregation modes

E’ possibile indicare per ciascun raw contact un aggregation_mode, aggiungendo una costante nella colonna RawContact. I possibili mode sono i seguenti:

  • AGGREGATION_MODE_DEFAULT — permette l’aggregazione automatica di default.
  • AGGREGATION_MODE_DISABLED — disabilita l’aggregazione automatica. Il contatto non verrà aggregato.
  • AGGREGATION_MODE_SUSPENDED — disattiva l’aggregazione automatica. Se il contatto fa parte di un raw contact aggregato, continuerà a farne parte anche se disattivata. Il contatto non farà match con altri contatti durante futuri tentativi di aggregazione automatica.

Aggregation exceptions

Per mantenere due contatti aggregati o disaggregati è possibile aggiungere la riga alla tabella ContactsContract.AggregationExceptions table. Le eccezioni presenti nella tabella saranno override di tutte le regole di aggregazione automatica.

Lookup URI

Per accedere alle informazioni sui contatti utilizzando le nuove Contact API di android è possibile utilizzare un riferimento ad un contatto piuttosto che accedere specificando l’id della riga. E’ possibile ottenere un lookup key dal contatto stesso, è una colonna della tabella ContactsContract.Contacts table. Ottenuto un lookup key è possibile costruire un URI nel seguente modo:  

Uri lookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);

e quindi utilizzarlo allo stesso modo di un tradizionale URI, ovvero: 

Cursor c = getContentResolver().query(lookupUri, new String[]{Contacts.DISPLAY_NAME}, ...);
try {
    c.moveToFirst();
    String displayName = c.getString(0);
} finally {
    c.close();
}

In sostanza ogni qual volta è richiesto un accesso ai dati di un contatto è consigliato farlo attraverso il lookup URI.
Prossima volta vediamo qualche esempio più interessante per ora questo è solo un assaggio.

Stay tuned!

First step(app) with android

Sto facendo porting di un’applicazione iPhone su Android, sto avendo modo di avvicinarmi ad entrambe le architetture valutandone pregi e difetti…pur mantenendo sempre il mio stile di programmazione…almeno fino a quando qualcuno di voi mi salverà :)

L’applicazione da portare su Android è la solita db based, quindi creato il database (sqlite) viene fatto il deploy all’interno dell’app. L’approccio è sempre lo stesso, ovvero la creazione di una classe singleton attraverso lo quale accedere al database, quindi connettere, disconnettere ed eseguire le query.
L’ambiente di riferimento su Android è Java, essendo il mio linguaggio di riferimento gioca tutto a mio favore. Il tool di sviluppo è Eclipse (forse chiamarlo tool è troppo riduttivo, facciamo IDE) e attraverso il plugin per android si parte praticamente subito allo sviluppo.

Creati i nostri package partiamo con la creazione della classe singleton: 

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
package it.devme.dbmanager;
 
 
import it.devline.model.Farm;
import it.devline.model.Month;
import it.devline.model.Season;
import it.devline.model.Vegetable;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
 
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
 
public class SQLiteDBManager extends SQLiteOpenHelper {
 
	private SQLiteDatabase dbconn;
	private static String DATABASE_NAME = "fennel.db";
	private static String DATABASE_PATH = "/data/data/it.devme/databases/";
	private final Context myContext;
	private static SQLiteDBManager instance;
 
	/**
	 * Costruttore
	 * @param context, il contesto dell'applicazione
	 */
	private SQLiteDBManager(Context context) {
	    super(context, DATABASE_NAME, null, 2);
		this.myContext = context;
	}
 
	public static SQLiteDBManager getInstance(Context context) {
		if (instance==null) {
			instance = new SQLiteDBManager(context);
		}
		return instance;
	}
 
	/**
	 * Tenta di aprire il database in sola lettura. Se il database non &egrave; presente,
	 * viene copiato dalla directory assets alla directory databases dell'applicazione.
	 * @return true se l'apertura del db va a buon fine; false altrimenti.
	 * @throws SQLException, eccezione in caso di errore.
	 */
	public boolean open() throws SQLException {
		boolean dbExist = checkDataBase();
 
		if(dbExist) {
    		//do nothing - database already exist
    	} else {
    		dbconn = this.getWritableDatabase();
        	try {
    			copyDataBase();
    		} catch (IOException e) {
        		Log.i("DBMANAGER", e.getMessage());
        		return false;
        	}
    	}
		return true;
	}
 
	/**
	 * Chiude il database.
	 */
	public synchronized void close() {
		 if(dbconn != null)
			 dbconn.close();
		 super.close();
	}
 
	@Override
	public void onCreate(SQLiteDatabase db) {
	}
 
	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
	}
 
	/**
	 * Effettua la copia del database dalla directory assets alla directory
	 * databases di default dell'applicazione.
	 * @throws IOException, eccezione in caso di problemi con il file
	 */
	public void copyDataBase() throws IOException {
 
	    InputStream assetsDB = myContext.getAssets().open(DATABASE_NAME);
	    OutputStream dbOut = new java.io.FileOutputStream(DATABASE_PATH + DATABASE_NAME);
 
	    byte[] buffer = new byte[1024];
	    int length;
	    while ((length = assetsDB.read(buffer))>0){
	      dbOut.write(buffer, 0, length);
	    }
 
	    dbOut.flush();
	    dbOut.close();
	    assetsDB.close();
	}	
 
	/**
	 * Restituisce true se il database &egrave; presente ed &egrave; possibile aprirlo;
	 * false altrimenti.
	 * @return true in caso di successo; false altrimenti.
	 */
	private boolean checkDataBase(){
		SQLiteDatabase checkDB = null;
 
		try {
			checkDB = SQLiteDatabase.openDatabase(DATABASE_PATH + DATABASE_NAME, null, SQLiteDatabase.OPEN_READONLY);
    	} catch(SQLiteException e) {}
 
    	if (checkDB != null) {
    		checkDB.close();
    	}
    	return checkDB != null ? true : false;
    }
 
    public List<String> getNames () {
	List<String> result = new ArrayList<String>();
	String sql = "SELECT name " +
		     "FROM Devme " +
		     "ORDER BY RANDOM()";
 
	Cursor cursor = dbconn.rawQuery(sql, null);
	while (cursor.moveToNext()) {
		result.add(cursor.getString(cursor.getColumnIndex("name")));
        }
	return result;    
    }
}

Si tratta di una classe Java (singleton) che realizza l’interazione con il database. Per utilizzarla è sufficiente ottenenerne l’istanza e quindi invocare il metodo che realizza la query voluta. Ovviamente prima bisogna connettersi, come segue: 

1
2
3
4
5
 .....
 .....
 SQLiteDBManager dbconn = SQLiteDBManager.getInstance(this);
 dbconn.open();
 List<string> vegs = dbconn.getNames();  dbconn.close();  <br /> ....      <br /> .... <br /></string>

Semplice, vero ?!
Questo è solo un assaggio per capire di quali potenzialità gode la piattaforma Android. Nei prossimi articoli, cercherò di occuparmi di esempi un pò più complessi per tirare in ballo altri argomenti.
Stay tuned ! 

SWF: Model Data Validation

spring-webflowDopo aver visto come SWF realizza il mapping tra i dati di un modello e i campi di un form, vediamo ora come creare il meccanismo di validazione che consente di verificare il valore di ciascun campo inserito all’interno di un form. In questo articolo abbiamo visto come viene realizzata il binding dei dati e teniamolo come punto di partenza per iniziare a vedere il meccanismo della validazione.
Riprendiamo il codice sorgente del file di configurazione del flow e la pagina HTML all’interno della quale è presente il form:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/webflow
        http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
 
    <var name="flight" class="it.devme.flight.Flight" />
 
    <view-state id="start" view="flight/insertFlightInfo" model="flight">
      <transition on="next" to="confirm" />
    </view-state>
 
    <view-state id="confirm" view="flight/showFlightInfo" />
 
</flow>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
 
<html>
  <head>
    <title>DevMe - SWF Flight info test - </title>
  </head>
 
<body>
  <h1>Flight information </h1>
  <p>Inserisci le informazioni sul volo:</p>
    <form:form id="flightDetails" modelAttribute="flight">
      <table>
        <tr>
          <td>Numero del volo: </td>
         <td><form:input path="number"/></td>
        </tr>
 
        <tr>
          <td>Ora di partenza: </td>
          <td><form:input path="time"/></td>
        </tr>
 
        <tr>
         <td>Gate: </td>
         <td><form:input path="gate"/></td>
        </tr>
 
        <tr>
          <td>Volo in ritardo: </td>
          <td><form:radiobutton path="isDelayed" value="false" />
  	      <form:radiobutton path="isDelayed" value="true" /></td>
        </tr>
 
        <tr>
          <td><input type="submit" name="_eventId" value="next"></td>
        </tr>
    </table>
    </form:form>
  </body>
</html>

Bene. Come già ampiamente discusso, il flow definito dal file XML di sopra è molto semplice. Abbiamo una sola transizione da uno stato all’altro, ovvero si naviga da una pagina ad un altra pagina. Supponiamo ora, di voler validare i dati del form della pagina di partenza, e spostarsi nella pagina di arrivo, solo se non esistono errori di validazione secondo il nostro personale schema di validazione. Il nostro schema, prevede per semplicità che almeno uno dei tre campi presenti nel form sia indicato, e che se indicato il numero del volo questi dovrà essere un valore numero e eventuali caratteri alfabetici verranno rigettati.

Iniziamo con la modifica del file XML di definizione del flow:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/webflow
        http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
 
    <var name="flight" class="it.devme.flight.Flight" />
 
    <view-state id="start" view="flight/insertFlightInfo" model="flight">
      <transition on="next" to="confirm" >
          <action method="bindAndValidate" bean="flightAction" />
      </transition>
    </view-state>
 
    <view-state id="confirm" view="flight/showFlightInfo" />
 
</flow>

Il metodo bindAndValidate in sostanza richiama la nostra classe validator, la quale effettua i controlli che andremo a definire. Da notare che il riferimento nel tag action, punta ad un metodo all’interno della nostra classe Action che in realtà non esiste, ma che viene ereditato dalla superclasse FormAction. Definendo all’interno del file XML di definizione del bean il riferimento alla nostra classe Validatore, autormaticamente verrà richiamata all’invocazione del metodo bindAndValidate. Il codice della classe che si occupa di effettuare la validazione è il seguente: 

package it.devme.flight;
 
public class FlightValidator implements Validator {
 
	public boolean supports(Class clazz) {
		return clazz.equals(Flight.class);
	}
 
	public void validate(Object target, Errors errors) {
                Flight flight = (Flight) target;
                if (flight.getNumber()==0 && flight.getTime()==null && flight.getGate.equals("")) {
                    errors.rejectValue("gate", "Attenzione, &egrave; necessario specificare almeno 1 tra: numero del volo, ora di partenza, Gate");
                }
 
                if (errors.getErrorCount()==0) {
                    ValidationUtils.rejectIfEmptyOrWhitespace(errors, "gate", "Il campo gate non pu&ograve; essere vuoto.");
                }
	}
 
}

Vediamo cosa è stato fatto. Intanto osserviamo che FlightValidator implementa la classe Validator, quindi definisce alcuni metodi essenziali che sono supports e validate. Il primo assicura il supporto al Bean indicato al suo interno. Il secondo implmenta la logica di validazione. Nel nostro caso vogliamo che almeno uno dei tre campi venga inserito, e che il campo Gate sia diverso da vuoto. Il primo controllo verifica la prima delle nostre condizioni; il secondo if, verifica prima che non ci siano stati errori in precedenza, e in caso positivo effettua il controllo di validazione sul campo Gate.

Vediamo ora come modifichiamo il file HTML per presentare il messaggio di errore:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
 
<html>
  <head>
    <title>DevMe - SWF Flight info test - </title>
  </head>
 
<body>
  <h1>Flight information </h1>
  <p>Inserisci le informazioni sul volo:</p>
    <form:form id="flightDetails" modelAttribute="flight">
      <table>
        <tr>
          <td>Numero del volo: </td>
         <td><form:input path="number"/></td>
        </tr>
 
        <tr>
          <td>Ora di partenza: </td>
          <td><form:input path="time"/></td>
        </tr>
 
        <tr>
         <td>Gate: </td>
         <td><form:input path="gate"/></td>
         <td><form:errors path="gate"/></td>
        </tr>
 
        <tr>
          <td>Volo in ritardo: </td>
          <td><form:radiobutton path="isDelayed" value="false" />
  	      <form:radiobutton path="isDelayed" value="true" /></td>
        </tr>
 
        <tr>
          <td><input type="submit" name="_eventId" value="next"></td>
        </tr>
    </table>
    </form:form>
  </body>
</html>

Per semplicità il nostro messaggio di errore comparirà sempre nello stesso posto, ovvero sotto il campo Gate. Così nel primo caso di errore, darà contezza del fatto che almeno uno dei campi di precedenti deve essere indicato, mentre nel secondo caso, sarà indicativo del campo stesso. Come vedete, l’aggiunta del tag <form:errors/> fa in modo di collegare eventuali codici di errori con il campo presentato in pagina.

Alla prossima. Stay tuned!

 

You need to log in to vote

The blog owner requires users to be logged in to be able to vote for this post.

Alternatively, if you do not have an account yet you can create one here.

Powered by Vote It Up

WordPress Themes