Posts tagged: android

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

WordPress Themes