Archives par étiquette : android

Android : Répéter une action (timer)

Dans android pour répéter une action à intervalles réguliers on peut utiliser timertask , mais pour d’obscures raisons , il m’est arrivé d’avoir des problèmes avec ce timer.
Du coup, une solution qui semble fonctionner à merveille est l’utilisation d’un handler et d’un runnable :

private Handler myHandler;
private Runnable myRunnable = new Runnable() {
@Override
public void run() {
    // Code à éxécuter de façon périodique

    myHandler.postDelayed(this,500);
    }
};

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.myLayout);

    myHandler = new Handler();
    myHandler.postDelayed(myRunnable,500); // on redemande toute les 500ms
}

public void onPause() {
    super.onPause();
    if(myHandler != null)
        myHandler.removeCallbacks(myRunnable); // On arrete le callback
}

On demande simplement au handler un appel retardé du runnable (ici 500ms) puis dans le runnable on répète cette opération.
On rentre ainsi dans une « boucle infinie » qui nous permet d’exécuter le code du runnable à intervale régulier.

Ne pas oublier de stopper le tout avant de quitter l’activité (ou avant) via removeCallback(Runnable).

Android upgrade sqlite

Une action qui peux se révéler délicate dans une application android c’est la mise à jour de la base de données sqlite lors de la publication d’une nouvelle version de votre app.
Le but étant d’arriver à modifier votre schema sans pour autant perdre les données que vous utilisateurs pourraient avoir enregistré.

Le problème qui se pose , c’est que sqlite à une fonction ALTER TABLE très limitée.
Vous pouvez renommer une table :

ALTER TABLE table1 RENAME TO table2

ou ajouter une colonne à vote table :

ALTER TABLE table1 ADD COLUMN newcolonne

Mais si vous avez besoin de modifier une contrainte ou encore le type d’un champs et bien ce n’est pas possible. il faut donc sauvegarder les données et les réinjecter dans une autre table.

Voici un petit exemple :

public class DBHelper extends SQLiteOpenHelper
{
	public DbHelper(Context context, String name, CursorFactory factory,int version) 
	{
		super(context, name, factory, version);
	}
	
	@Override
	public void onCreate(SQLiteDatabase db) 
	{
		//Création des tables
	}
	
	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 
	{
		//Mise à jour de la bdd de v1 à v2
		if(oldVersion == 1 && newVersion == 2)
		{
			try {
				db.beginTransaction();
				//Création de la nouvelle table, avec toute les modifications
				db.execSQL("CREATE TABLE table1_tmp (a,b)"); 
				//Transfert des données de table1 vesr table1_tmp
				db.execSQL("INSERT INTO table1_tmp (a,b) SELECT (a,b) FROM table1");
				//Suppression de l'ancienne table
				db.execSQL("DROP TABLE IF EXISTS table1");
				//Renomage de la table nouvellement créée afin de prendre la place de celle supprimée
				db.execSQL("ALTER TABLE table1_tmp RENAME TO table1");
				db.setTransactionSuccessful();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				db.endTransaction();
			}
		}
	}
}

On utilise ici une transaction pour s’assurer de ne pas perdre de données au passage. Si jamais quelque chose se passe mal , les modifications seront rollback automatiquement.

Ajouter un Custom Listener à un Widget personnalisé

Lorsque que l’on écrit un widget ou vue personalisé , les Listeners de base tel que onClickListener ne sont parfois pas suffisants. On peut avoir besoin de déclarer ses propres listeners qui répondront à des cas très précis.

Voici un exemple générique d’implémentation de listener personalisé ou custom listener.

Interface :

Nous supposons que votre widget est déjà en partie crée. Dans notre exemple il s’appelera MyCustomView.
La première étape est de créer une interface qui va gérer le Listener :

public interface OnMyCustomeEventListener {
	public abstract void onMyCustomEvent();
}

Cette interface ne comporte qu’une méthode abstraite pour rester simple , mais il est bien entendu possible de faire quelque chose de plus complexe avec plusieurs méthodes à redéfinir.

Modification de la vue personnalisée :

Passons à la vue. je n’ai garder que le code qui nous intéresse ici :

class MyCustomView extends View
{
	private OnMyCustomEventListener eventListener = null;

	/*
	* Le code de votre widget
	*/

	public void setOnMyCustomEventListener(OnMyCustomeEventListener listener)
	{
		this.eventListener = listener;
	}

	private void onMyCustomEvent()
	{
		if(eventListener != null)
			eventListener.onMyCustomEvent();
	}
}

1- On commence avec une variable qui contiendra une instance de notre listener.
2- On ajoute ensuite un setter setOnMyCustomEventListener pour que l’on puisse définir notre listener depuis l’activity.
3- On termine ensuite avec une méthode onMyCustomEvent que vous pourrez appelez ou vous voudrez dans votre code.

L’intérêt de cette dernière méthode c’est que l’on va y faire tous les tests nécessaires à la réalisation de l’événement. Nous testons dans l’exemple l’existance d’une instance de OnMyCustomeEventListener mais on pourrait imaginer vérifier d’éventuels paramètres conditionnant l’execution de l’événement.
Une fois ces 3 étapes ajoutées à votre widget , le plus gros du travail est fait. Il ne reste plus qu’à définir votre listener dans l’activity

Ajout du Listener :

public class MyActivity extends Activity
{
	private OnMyCustomeEventListener MyCustomViewListener = new OnMyCustomeEventListener () {
		@Override
		public void onMyCustomEvent()
		{
			//Ecrire ici votre événement
		}
	}

	public void onCreate(Bundle savedInstanceState)
	{
		MyCustomView myView = (MyCustomView) findViewById(R.id.myView);
		myView.setOnMyCustomEventListener(MyCustomViewListener);
	}
}

Dans notre activity on créer une variable qui instancie notre interface. Cette variable est donc notre listener. Ne reste ensuite qu’à passer ce listener à notre vue personnalisée via le setter précédemment défini.

Partager un arrayList d’objet entre activity via Parcelable

Imaginons le cas classique d’une activité A , d’une activité B et d’un objet Personne. Comment transférer un arrayList de Personne entre A et B ?

Il faut simplement implémenter Parcelable pour l’objet personne et créer un liste d’objet elle même Parcelable.

Commençons par notre objet Personne très simple :

Class Personne
{
	private String nom;
	private String prenom;

	public Personne(){}
	public Personne(String nom,String prenom)
	{
		this.nom 		= nom;
		this.prenom = prenom;
	}

	public void setNom(String nom){ this.nom = nom;}
	public void setPrenom(String prenom){ this.nom = prenom;}

	public String getNom(){return this.nom;}
	public String setNom(){return this.prenom}
}

Rendre l’objet Parcelable

Implémentons l’interface Parcelable :

Class Personne implements Parcelable
{
	//Même chose que précédent
	[...]

	//Second constructeur qui sera appelé lors de la "Deparcelablisation"
	public Personne(Parcel in)
	{
		this.getFromParcel(in);
	}

	@SuppressWarnings("rawtypes")
	public static final Parcelable.Creator CREATOR = new Parcelable.Creator()
	{
		public Personne createFromParcel(Parcel in)
		{
			return new Personne(in);
		}

		@Override
		public Object[] newArray(int size) {
			return null;
		}
	};

	@Override
	public int describeContents() {
		return 0;
	}

	//On ecrit dans le parcel les données de notre objet
	@Override
	public void writeToParcel(Parcel dest, int flags)
	{
		dest.writeString(this.nom);
		dest.writeString(this.prenom);
	}

	//On va ici hydrater notre objet à partir du Parcel
	public void getFromParcel(Parcel in)
	{
		this.setNom(in.readString());
		this.setPrenom(in.readString());
	}
}

Les deux méthodes importantes sont writeToParcel et getFromParcel qui permettent respectivement d’écrire dans le Parcel et de lire dedans. Pour simplifier on « sérialise » l’objet avec writeToParcel et on le « déserialise » avec getFromParcel.
Notez que getFromParcel peut se nommer comme bon vous semble.
L’ordre des variables dans writeToParcel est important puisque vous devrez utiliser le même dans getFromParcel.
Vous avez à cet instant un objet Parcelable , ce qui veux dire que vous pouvez le passer d’une activité à une autre au travers d’un Bundle. Notre but étant de passer une liste il nous reste encore un peu de travail.

Créer un arrayList Parcelable

Pour pouvoir transférer une liste d’objet on va créer une liste de personne qui dérive de ArrayList et qui implémente parcelable. On reprend exactement le même principe que pour notre objet :

class PersonneList extends ArrayList<Personne> implements Parcelable
{
	public PersonneList()
	{

	}

	public PersonneList(Parcel in)
	{
		this.getFromParcel(in);
	}

	@SuppressWarnings("rawtypes")
	public static final Parcelable.Creator CREATOR = new Parcelable.Creator()
	{
		public PersonneList createFromParcel(Parcel in)
		{
			return new PersonneList(in);
		}

		@Override
		public Object[] newArray(int size) {
			return null;
		}
	};

	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flags)
	{
		//Taille de la liste
		int size = this.size();
		dest.writeInt(size);
		for(int i=0; i < size; i++)
		{
			Personne pers = this.get(i); //On vient lire chaque objet personne
			dest.writeString(pers.nom);
			dest.writeString(pers.prenom);
		}
	}

	public void getFromParcel(Parcel in)
	{
		// On vide la liste avant tout remplissage
		this.clear();

		//Récupération du nombre d'objet
		int size = in.readInt();

		//On repeuple la liste avec de nouveau objet
		for(int i = 0; i < size; i++)
		{
			Personne pers = new Personne();
			pers.setNom(in.readString());
			pers.setPrenom(in.readString());
			this.add(pers);
		}

	}
}

Nous avons donc désormais un objet parcelable ainsi qu’une liste. Il ne nous reste plus qu’à peupler la liste d’objet et de transmettre la liste entre les activités :

Intent myIntent = new Intent(Activity_A.this,Activity_B.class);
myIntent.putExtra("identifiantListe",(Parcelable)monObjet_personneList);
startActivity(myIntent);

Pour récupérer la liste d’objet dans l’autre activité il suffit de récupérer le bundle :

public void onCreate(Bundle savedInstanceState)
{
	super.onCreate(savedInstanceState);
	setContentView();

	Bundle b 	= getIntent().getExtras();
    PersonneList maliste 	= b.getParcelable("identifiantListe");
}

Et si mon objet contient d’autres objets ?

Il n’est pas rare d’avoir un objet qui en contient d’autre. Dans ce cas comment faire puisque Parcel n’a que des méthode de lecture et d’écriture de type primitifs ?
Et bien tout d’abord il faut que cet objet inclus soit lui aussi Parcelable. Puis dans les deux méthodes writeToParcel et getFromParcel il vous suffit d’écrire/lire cet objet via writeParcelable et readParcelable.
Exemple :

//On ecrit dans le parcel les données de notre objet
@Override
public void writeToParcel(Parcel dest, int flags)
{
	dest.writeString(this.nom);
	dest.writeString(this.prenom);
	dest.writeParcelable(this.listeAnimaux,flags); // On écrit la liste des animaux de la personne
}

//On va ici hydrater notre objet à partir du Parcel
public void getFromParcel(Parcel in)
{
	this.setNom(in.readString());
	this.setPrenom(in.readString());
	// On lit la liste des animaux de la personne
	this.listeAnimaux = in.readParcelable(ListeAnimaux.class.getClassLoader());
}

Vous devriez normalement avec cette méthode faire transitez tous les objets ou liste d’objets que vous souhaitez entre deux activités.