Posts tagged: design pattern

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 è 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 è presente ed è 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 ! 

iPhone: NavigationController e Session Manager

iphone-guitarBentornati su devme! In questo articolo volevo parlare di una strategia che ho impiegato nel progetto di lavoro di cui mi sto occupando, che tratta dello sviluppo di un’applicazione per iPhone (e altre piattaforme), e del quale spero se ne sentirà parlare. Non presenterò una discussione sul funzionamento dell’applicazione, ma di un aspetto particolare che ho utilizzato per risolvere un problema. Volevo proporre la mia soluzione (sicuramente ne esisteranno delle altre più ottimizzate) e discuterne con chi ne fosse interessato.
L’analogia che si può riscontrare è quella web. Si pensi ad esempio alla navigazione tra pagine web, ciascuna delle quali visualizza il suo contenuto sulla base delle scelte effettuate nelle pagine precedenti. Nel contesto di un’applicazione molto semplice si potrebbe far uso delle Sessioni messe a disposizione dal linguaggio utilizzato per lo sviluppo (es. php). Il valore selezionato nella prima pagina viene memorizzato in Sessione e recuperato nella pagina finale per l’estrazione dei dati di dettaglio, di un’ipotetica scheda prodotto. Nella mia applicazione ho utilizzato la stessa tecnica, in questo contesto mi è sembrata la più semplice…ovviamente ditemi se esiste di meglio :)
L’applicazione è molto varia, utilizza molti componenti messi a disposizione dall’ SDK iOS, e cioè TabbedBar, NavigationController, gestione degli eventi, TableViewController, ricerche su tabelle e altro. Nello specifico, tratteremo della navigazione attraverso delle view utilizzando il NavigationController.
Ma passiamo al codice: 

@@ SessionManager.h @@
 
#import <Foundation/Foundation.h>
#import "SessionObject.h"
 
@interface SessionManager : NSObject {
 
	NSMutableDictionary *session;
}
 
@property (nonatomic, retain) NSMutableDictionary *session;
 
/** 
 * Get singleton instance of Session Manager.
 */
+(SessionManager*) getInstance;
 
/**
 * Init session object.
 */
-(void) initSession;
 
/**
 * Put object into session.
 */
-(void) put: (NSString*)key object:(NSObject*)object;
 
/**
 * Retrieve object from session if exists.
 */
-(NSObject*) get: (NSString*)key;
@end
 
#import "SessionManager.h"
 
@@ SessionObject.m @@
 
@implementation SessionManager
 
@synthesize session;
 
static SessionManager *instance = nil;
 
+(SessionManager*) getInstance {
    @synchronized([SessionManager class]) {
        if (!instance)
            [[self alloc] init];
	return instance;
    }
    return nil;
}
 
+(id) alloc {
    @synchronized([SessionManager class]) {
        NSAssert(instance==nil, @"Attempted to allocate a second instance of SessionManager singleton");
	instance = [super alloc];
	return instance;
    }
    return nil;
}
 
-(id) init {
    self = [super init];
    if (self != nil) {
	[self initSession];
    }
    return self;
}
 
-(id) retain {
    return self;
}
 
-(void) initSession {
    session = [[NSMutableDictionary alloc] init];
}
 
-(NSObject*) get:(NSString *)key {
    NSObject *obj = [session objectForKey:key];
    return obj;
}
 
-(void) put:(NSString *)key object:(NSObject *)object {
    [session setObject:object forKey:key];
}
 
@end

La prima parte del codice fa riferimento alla definizione del file header per la classe SessionManager, ovvero la classe che memorizzerà le informazioni in sessione. La classe è un singleton, esattamente indentica a quella vista in questo articolo. Possiede un’unica variabile membro, un array associativo NSMutableDIctionary che consente la memorizzazione di oggetti in sessione, allo stesso modo in cui avviene ad esempio in php ($_SESSION["key"] = $value). Nel nostro caso il valore in sessione viene memorizzato attraverso l’invocazione del metodo put, il quale prende in input 2 parametri, la chiave e il valore e lo memorizza in Sessione. Il metodo simmetrico, consente di estrarre il valore dalla sessione attraverso la specifica della chiave. Possiede i metodi di inizializzazione che consentono di allocare e inizializzare l’array associativo.

Consideriamo ora di aver creato una classe NavigationController per permettere la navigazione tra le diverse view, e che la prima delle nostre view sia di tipo UITableViewController. Senza scendere nei particolari vediamo solo i metodi più signitficativi della nostra classe:

#import "DevmeTableViewController1.h"
 
@implementation DevmeTableViewController1
 
@synthesize devmeTableViewController2;
 
- (void)viewDidLoad {
    [super viewDidLoad];
    //&nbsp;Populate datasource table with Car object
    [self loadTableContent];
    UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Tabella1" style:UIBarButtonItemStylePlain target:nil action:nil];
    self.navigationItem.backBarButtonItem = backButton;
    [backButton release];
    self.navigationItem.title = @"Dati tabella 1";	
}
 
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
 
    // Configure the cell...
    Car *car = [datasource objectAtIndex:indexPath.row];
    // Display cars brand
    cell.textLabel.text = car.brand;
    return cell;
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    Car *car = [datasource objectAtIndex:indexPath.row];
    session = [SessionManager getInstance];
    [session put:@"BRAND" object:car.brand];
 
    DevmeTableViewController2 *tvController = [DevmeTableViewController2 alloc];
    self.devmeTableViewController2 = tvController;
    [tvController release];	
    [self.navigationController pushViewController:self.devmeTableViewController2 animated:YES];
}
 
..... other methods defined here.....
.....
.....
@end

La classe DevmeTableViewController1 visualizza un elenco di marche automobilistiche. La tabella viene popolata dal metodo loadTableContent omesso per semplicità, ma molto semplice da comprendere. Il metodo restituisce l’elenco delle marche automobilistiche estratte da un database popolando l’array datasource. Il metodo interessante per i nostri scopi è didSelectRowAtIndexPath il quale si occupa della navigazione per così dire, da una view ad un’altra. In particolare osserviamo all’interno del metodo che viene selezionato il valore associato alla cella della tabella che è stata selezionata (tapped), estraendolo dal datasource. Lo stesso valore viene salvato in sessione attraverso l’invocazione del metodo put sull’oggetto singleton SessionManager con chiave BRAND.
La view di arrivo è anch’essa una UITableViewController, la quale estrae il valore dalla sessione e popola opportunamente la tabella:

#import "DevmeTableViewController2.h"
 
@implementation DevmeTableViewController2
 
@synthesize devmeTableViewController3;
 
.... other methods here
 
- (void)viewDidLoad {
    [super viewDidLoad];
    session = [SessionManager getInstance];
    NSString *brand = (NSString*) [session get:@"BRAND"];
    self.navigationItem.title = brand ;
    [self loadTableContent:brand];	
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
   session = [SessionManager getInstance];
   [session put:@"MODEL" object:[datasource objectAtIndex:indexPath.row]];
 
   DevmeTableViewController3 *vdController = [DevmeTableViewController3 alloc];
   self.devmeTableViewController3 = vdController;
   [vdController release];
   [self.navigationController pushViewController:self.devmeTableViewController3 animated:YES];	
}
 
.... other methods here
@end

Anche in questo caso abbiamo solo i metodi più significativi, viewDidLoad il quale estrae il valore dalla sessione e popola la tabella invocando loadTableContent passando come parametro il valore selezionato in precedenza. Mentre il metodo didSelectRowAtIndexPath preleva il valore selezionato e lo inserisce in Sessione, rendendolo disponibile così alla view successive, infine sposta la navigazione verso l’ultima delle View. Per semplicità non vedremo il codice dell’ultima view, il quale è una ripetizione di quanto già visto. Possiamo considerare l’ultima view una semplice UIViewController, la quale preleva i valori dalla Sessione e invoca un metodo di estrazione dei dati dal database fornendo in input i 2 valori.
E’ tutto. Come vedete delle semplici analogie web possono essere utilizzate nel contesto di un’applicazione per iPhone, rendendo a mio avviso, facile la gestione. Questa applicazione verrà portata anche su dispositivi mobili che montano Android, nei prossimi articoli vedremo qualche esempio di quell’applicazione.
Alla prossima. Stay tuned !

 

WordPress Themes