mardi 21 février 2012

Sauvegarder l'état des variables d'une activité Android lors de la rotation

Si vous développez des appli Android, il vous est sûrement déjà arrivé de devoir remplir passer des données d'une activité à une autre, et de vous apercevoir que lorsque vous tourniez votre téléphone les données du bundle étaient perdues.

Cela est une problématique classique du cycle de vie d'une activité sur Android. En effet, lors d'une rotation, votre activité est détruite, et elle se rappelle elle-même en quelque sorte. De ce faite, toutes les données qui avaient été stockées dans les variables automatiques sont initialisées à nouveau. Une façon de conserver ces données serait de passer les variables en statiques, et c'est une erreur que je considérerais de débutant, que j'ai faite à mes débuts. Sur le principe cela fonctionne, mais le code n'est pas propre.

Le meilleure pratique est, je pense, de considérer la surcharge de la méthode onSaveInstanceState(Bundle icicle) de la classe Activity. Prenons une activité classique:

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;


public class testActivity extends Activity
{
 
 String test;
 
 @Override
 protected void onCreate(Bundle savedInstanceState)
 {
  super.onCreate(savedInstanceState);
  test = getIntent().getExtras().getString("test", "test perdu");
  Log.d("test bundle", test);
 }

}

Si vous avez lancé l'activité depuis une autre activité de cette façon:

Intent i = new Intent(this, testActivity.class);
i.putExtra("test", "test ok");
startActivity(i);

Votre logcat va afficher test ok.
Si vous faites une rotation de l'écran, votre logcat affiche test perdu. C'est là qu'intervient onSaveInstanceState(Bundle icicle). Celui-ci va être appelé avant la destruction de l'activité. Nous allons donc l'implémenter ainsi:

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class testActivity extends Activity
{
 String test;

 @Override
 protected void onCreate(Bundle savedInstanceState)
 {
  super.onCreate(savedInstanceState);

  test = getIntent().getExtras().getString("test");
  if (test == null) savedInstanceState.getString("test", "test non défini");

  Log.d("test bundle", test);
 }

 @Override
 protected void onSaveInstanceState(Bundle outState)
 {
  outState.putString("test", test);
  super.onSaveInstanceState(outState);
 }

}

Comme on peut le voir ici, on place dans le bundle outState toutes les données que l'on veut sauvegarder. Nous avons légèrement modifier le traitement de la méthode onCreate(Bundle savedInstanceState) pour récupérer ces données que nous avons stockée dans outState. Donc on récupère, par exemple, les extras qui auraient du être passés par l'activité précédente. Si ces extras ont disparu à la rotation, on regarde si l'on ne peut pas les récupérer dans le savedInstanceState, qui est une copie du bundle outState précédent.

Bien sûr, cela ne fonctionne pas qu'avec les String, toutes les données stockables dans un bundle peuvent être sauvegardées dans outState de onSaveInstance et récupérée dans le savedInstance de onCreate après la rotation.

Vous pouvez désormais vous passer de ces vilaines variables statiques que l'on ne saurait voir et faire des rotations d'activité en conservant vos données :)

vendredi 10 février 2012

Liste d'arguments variable en objective-C et Java


Pour changer aujourd'hui un post non pas axé sur un language mais sur une façon de coder. Je vais vous expliquer comment créer une fonction varargs ou en français variadique, c'est à dire une fonction qui prend un nombre arbitraire d'opérandes.

Il vous est sûrement déjà arrivé de créer des fonctions que prennent des tableaux d'objets en paramètre. C'est très bien quand on ne sait pas quels éléments vont constituer le tableau, mais lorsque savez que vous coderez toujours en dur les paramètres de la fonction, je ne pense pas qu'il soit judicieux de créer et allouer un tableau. Voici comment je procède en Objective-C sur iPhone en Java sur Android.

Sur iPhone, tout d'abord, dans le .h, je déclare par exemple ceci:

- (void) maFonctionAvecUnParametre:(id)param1
        etUneListeVariable:(id)param2,...
        NS_REQUIRES_NIL_TERMINATION;

Vous remarquerez que le paramètre "etUneListeVariable" et constitué d'un paramètre, ici de type id car je n'ai pas voulu me limiter à un seul type, trois points et une macro système. Voici comment les interpréter:

  • le paramètre va définir tout simplement le type des arguments qui vont suivre
  • les trois petits points suivant une virgule annoncent que le nombre de paramètres est variable
  • le NS_REQUIRES_NIL_TERMINATION n'est pas indispensable, c'est une macro qui déclare au compilateur que la liste des paramètres doit se terminer par un "objet nul", soit nil en objective-C.
l'utilisation dans le .m se fait de cette façon:

-(void) maFonctionAvecUnParametre:(id)param1 etUneListeVariable:(id)param2,...
{
    va_list args;
    va_start(args, param2);

    id valeur = param2;

    //si le premier argument des varargs n'est pas nul
    if(valeur)
    {
        //on boucle sur les éléments la liste des paramètres variable en commençant par le premier, ici param2 
        do {
         //ici mes traitement sur l'objet valeur
        } while ((valeur = va_arg( args, id )));
    }
}
Exemple d'utilisation:

[monObjet maFonctionAvecUnParametre:toto etUneListeVariable:titi, tata, tutu, nil];

[monObjet2 maFonctionAvecUnParametre:toto etUneListeVariable:titi, tata, tutu, tyty, nil];


Voilà pour l'objective-C.
En ce qui concerne le Java et entre autres Android, rien de plus simple, trois petits points suffisent, le langage assimile la liste lui-même à un tableau:


private static void traiterMaListe(Object... params)
{
     for (Object param : params)
     {
          //traitement sur mon param
     }
}

Exemple:

monObjet.traiterMaListe(toto, tata, titi);
monObjet2.traiterMaListe(toto, tata, titi, tutu, tyty);

Pratique n'est-ce pas :)

Voilà pour mon domaine de compétence. Il faut savoir que la syntaxe d'objective-C est directement issue du C, et que d'autres langages comme le PHP par exemple implémentent les fonctions variadiques. Si vous voulez les implémenter dans un de ces langages, cherchez tout simplement varargs + votre langage sur Google, ou regardez sur Wikipedia :)

mardi 1 novembre 2011

"???????????? no permissions" pour le Motorola Atrix sur Fedora

Etant récemment passé sur une Linux Fedora 15, je me suis retrouvé confronté à un problème assez récurrent d'ADB sur cette plateforme, le fameux:

$ adb devices
List of devices attached
???????????? no permissions


Ce problème étant un problème parfaitement connu, il est traité sur le site Android Developers. Malheureusement, il s'avère que sous Fedora, les solutions proposées ne fonctionnent pas pour mon Motorola Atrix. Voici une solution qui s'en inspire et qui fonctionne.



Ouvrez une ligne de commande et créez une règle de configuration USB pour UDEV. Pour cela tapez:


sudo vim /etc/udev/rules.d/98-android.rules


Une fois votre éditeur ouvert (vim, vi, gedit, ou autre), ajoutez la ligne suivante au fichier (appuyez sur la touche i sous vi/vim):

SUBSYSTEM=="usb", ATTRS{idVendor}=="22b8", SYMLINK+="android_adb", MODE="0666", OWNER="climbatize"


Ici, remplacez 22b8 par le vendor id de votre constructeur (on peut l'obtenir par un simple lsusb, l'ID étant le chiffre avant les deux points après ID), et le owner par votre propre pseudo. Sauvegardez, quittez l'éditeur (echap :wq sous vi/vim).

Rechargez les règles de UDEV:

sudo udevadm control --reload-rules


Débranchez votre device, redémarrez adb:

adb kill-server
adb start-server-server


Si tout s'est bien passé, adb devices devrait vous retourner votre device. Si non, vous pouvez toujours temporairement lancer adb en mode administrateur:


sudo adb kill-server
sudo adb start-server


jeudi 2 décembre 2010

Sniffer les transactions réseau sur Android

Récemment, je me suis retrouvé confronté à un problème dans une appli Android. En effet, cette appli utilisait une API privée pour envoyer des statistiques d'utilisation aux serveurs de Webtrends (une bibliothèque que je ne saurais trop conseiller, préférez-lui Google Analytics, mais là n'est pas le sujet).

Ainsi, non content de ne pas avoir de message d'erreur dans la console, nous envoyons l'appli à notre client, qui nous informe que les données analytiques ne sont pas remontées. Il a fallu trouver une solution pour "sniffer" les transactions éventuelles de Webtrends.




C'est ainsi que j'ai découvert "Shark for root" sur le market, ainsi que son "Shark Reader" pour lire les résultats. A noter que la première appli nécessite les droit de super-utilisateur (root) sur votre terminal, Et que la seconde est d'un intéret limité sans la première :)

En faisant une recherche Google, je viens de trouver ceci à propos de l'appli, par l'auteur, avec les apk qui vont bien :) http://forum.xda-developers.com/showthread.php?t=725692

Shark for root:


Shar reader:

mardi 3 août 2010

Créer son premier projet Android

Après avoir installé notre SDK Android sur Eclipse et configuré notre Android Virtual Device (AVD) nous voilà fins prèts à créer ce fameux projet Android.


  • Première étape, il faut bien penser à configurer l'emplacement du SDK dans les paramètres d'Eclipse. Pour cela nous allons dans le menu Fenètres > Préférences d'Eclipse.

    Dans la fenètre de préférence sélectionnez le menu Android et ici donnez le chemin de votre SDK (dans mon cas /home/climbatize/android-sdk-linux_86). Si le chemin est bon, lorsque vous cliquez sur "Appliquer", la liste de vos API devrait apparaître:







    Voilà pour la config.


  • Maintenant créons gaiement notre projet. Rien de bien compliqué là non plus, Fichier > Nouveau > Autre...

    Dans le champs de recherche tapez "Android":




    Sélectionnez "Android Project".
     Cliquez sur Suivant.


    Donnez un nom à votre projet. Si vous avez intallé les samples pour un SDK et que vous souhaitez les compiler pour tester, sélectionnez ce SDK et les samples apparaîtront.


    Si vous désirez partir de zéro, remplissez tous les champs obligatoires. (Nom de projet, package, etc...) ces noms ne sont pas important pour votre première appli, mais sont obligatoires.




    Voilà, c'est le stricte minimum dont vous avez besoin pour créer votre projet. Cliquez sur terminer.



  • Comme vous pouvez le constater, votre projet contient déjà des erreurs. C'est un bug du SDK Android pour Eclipse:


    On se retrouve donc avec l'erreur "The project cannot be built until build path errors are resolved" ou encore "Project ‘Nom du projet’ is missing required source folder: ‘gen’".

    Autant vous dire que j'en ai testé des combinaisons de build path avant de corriger cette erreur :)

    Pas de panique, rien de bien sorcier à corriger au final, il suffit simplement de supprimer le firchier R.java dans les sous-dossiers de "Gen".

  • Et voilà, c'est vraiment fini, les vrais challenges peuvent commencer! Rendez-vous dans les prochain post pour vos balbutiement de code, ou mieux, essayez de pondre quelque chose par vous-même ^^

    Twitter