Facebook Twitter Google Plus RSS Feed Email

    samedi 19 avril 2014

    [Tutoriel 2/2] :Communication Android avec MySQL+BONUS

    Bienvenue ,ceci est la deuxième partie du tutoriel précédant [Tutoriel] :Communication Android avec MySQL [PHP] [ Partie 1/2 ] .
    Notre objectif est de faire communiquer notre application Android avec une base de données MySQL et faire différents opérations (CRUD).
    Comme le titre indique il y a  un BONUS ;)


    Voici le résultat final de ce tutoriel  :



    Je sais que vous allez "scroller" jusqu'à la fin pour voir le Bonus , mais , je vais commencer par  ce bonus tout d'abord Smiley  

    Partie 0 : Bonus : ProgressDialog personnalisé:

    CustomProgressDialog

    Plusieurs entre vous développent des applications android  qui nécessitent que l'utilisateur attend un peu du temps afin que le traitement soit terminé . Pour cela on utilise des progressDiaolg pour demander de l'utilisateur de patienter ou l'informer . 
    Je vais vous montrer comment personnaliser un progressDialog moderne . 
    Tout d'abord on crée un nouveau projet Android (vous le savez comme même Smiley)
    On va créer une classe "CustomProgressDialog" qui s'étend  de la classe "Diaolg" : et avec un ctrl + shift + o tout va bien :


    public class CustomProgressDialog extends Dialog {
        
        private ImageView iv;
         public CustomProgressDialog(Context context, int resourceIdOfImage) {
            super(context, R.style.TransparentProgressDialog);
                WindowManager.LayoutParams wlmp = getWindow().getAttributes();
                wlmp.gravity = Gravity.CENTER_HORIZONTAL;
                getWindow().setAttributes(wlmp);
            
            setCancelable(false);
            setOnCancelListener(null);
            LinearLayout layout = new LinearLayout(context);
            layout.setOrientation(LinearLayout.VERTICAL);
            LinearLayout.LayoutParams params = new     LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            iv = new ImageView(context);
            iv.setImageResource(resourceIdOfImage);
            layout.addView(iv, params);
            addContentView(layout, params);
            
        }
            
        @Override
        public void show() {
            super.show();
            RotateAnimation anim = new RotateAnimation(0.0f, 360.0f , Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f);
            anim.setInterpolator(new LinearInterpolator());
            anim.setRepeatCount(Animation.INFINITE);
            anim.setDuration(3000);
            iv.setAnimation(anim);
            iv.startAnimation(anim);
        }
    }
     

    - Il nous reste qu'ajouter la configuration du style dans le fichier "res/values/styles.xml" :


    <!--  Custom dialog -->
        <style name="CustomProgressDialog" parent="@android:Theme.Dialog">
            <item name="android:windowFrame">@null</item>
            <item name="android:windowBackground">@android:color/transparent</item>
            <item name="android:windowIsFloating">true</item>
            <item name="android:windowContentOverlay">@null</item>
            <item name="android:windowTitleStyle">@null</item>
            <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
            <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
            <item name="android:backgroundDimEnabled">true</item>
            <item name="android:background">@android:color/transparent</item>
        </style> 


    - Pour tester ce progressDialog , il suffit d'instancier la classe et appeler la méthode "show":
       
    CustomProgressDialog  progressDialog = new CustomProgressDialog(this, R.drawable.loading_throbber);
            progressDialog.setCancelable(true);
            progressDialog.show();
            //progressDialog.dismiss();
                   
    - Ajouter cette photo dans le dossier drawable  (vous pouvez la changer par un autre modèle) :

       Partie 1 :  Let's code:

    Revenons à notre objectif principal : communiquer notre application Android avec une base de données MySQL et faire différents opérations (CRUD).
    Comme on vu dans la première partie, notre base (avec un seul table et 4 champs ) est un peu générale , vous pouvez travaillez sur vos base .


    Voici le concept général de  notre application :

           O: Avant de commencer : AsyncTask , HTTP request , parsing ??  :
    Il y a des notions qu'on va les utiliser dans le développement de notre application:


    1. AsyncTask: vous permet de faire proprement et facilement des opérations en parallèle du thread UI. Cette classe permet d'effectuer des opérations d'arrière-plan et de publier les résultats dans le thread UI sans avoir à manipuler de threads ou de handlers.
     
        
    private class AddDataAsyncTask extends  AsyncTask<Void, Void, Void> {
            @Override
            protected void onPreExecute() {
                // TODO Auto-generated method stub
                super.onPreExecute();
                // faire des traitement avant mettre des requétes : montrer progressDialog par exmple
            }
            @Override
            protected Void doInBackground(Void... params) {
                // TODO Auto-generated method stub
                // ici on fait les requetes ,retourner les reponser et transformer les données JSON (Parsing)
                return null;
            }
            @Override
            protected void onPostExecute(Void result) {
                // TODO Auto-generated method stub
                super.onPostExecute(result);
                // ici on écrit le code aprés l'exécution de doInBackground
                // par exmemple : cache le progressDialog , afficher les résultats etc..
            }
           
        }

       

     Remarques:
    • Éviter les traitements coté U/I dans la méthode doInBackgound (  passage entre activités , cache le progressDialog etc.. )
    • Essayer toujours de faire des logs (afficher les résultats dans le logcat ) pour découvrir facilement les erreurs. (même après chaque ligne Log.i("msg",monValeur))
    • Il faut bien connaître le retour de vos fonctions  PHP (JSON) afin de les récupérer . (JSONObject, JSONArray etc. )
    • VERIFIER QUE LA PERMESSION INTERNET EST AJOUTÉE SVP .
    • Essayer d'utiliser l’émulateur Genymotion : Rapide , performant
    Pour faire des requêtes HTTP et retourner la réponse , on va utiliser utiliser une classe développée par Ravi Tamada dans son fameux site des tutoriels AndroidHive : ServiceHandler:elle va nous facilite les choses Smiley 


             1: ActivityMain :Ajouter des données à la base :
    Cette activité a pour but ,d'ajouter des données à la base insérées par l’utilisateur.
    Voici le code xml du layout activity_main : http://pastebin.com/TE7whc3w 
    MainActivity
     

    Avant de passer à coder  Smiley ,rappelons qu'on va utiliser le fichier ajout_bd.php
      Donc on va transmettre 3 valeurs : col2, col3 et col4 
    ==> HTTP POST request
     et on a comme résultat : un seul objet (JSONObject) qui contient un entier et une chaîne de caractères (String)
    Let's code !

    Dans la classe MainActivity  , on va déclarer des variables globales  (avant on Create()) :
         
    CustomProgressDialog  progressDialog;
        Button ajout,annuler;
        EditText col2Valeur,col3Valeur,col4Valeur;
        String urlAdd="http://address_IP_du_PC/enis_android_club/ajout_bd.php";
        AddDataAsyncTask AddData; // instance de notre asyncTask
        String message;
        int success;
     


     - Dans la méthode onCreate ,on définit les variables (les boutons etc.. ) , les listeners des boutons etc. :
     
    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            progressDialog = new CustomProgressDialog(this, R.drawable.loading_throbber);
            progressDialog.setCancelable(true);
            ajout=(Button)findViewById(R.id.ajout);
            annuler=(Button)findViewById(R.id.annuler);
            col2Valeur=(EditText)findViewById(R.id.col2);
            col3Valeur=(EditText)findViewById(R.id.col3);
            col4Valeur=(EditText)findViewById(R.id.col4);
            AddData =new AddDataAsyncTask();
            ajout.setOnClickListener(new OnClickListener() {
                 @Override
                public void onClick(View arg0) {
                    AddData.execute();// exécuter l'asyncTask
               
                }
            });
        }

    - Maintenant on va définir notre asyncTask pas à pas  :Dans la même classe Main , on crée une classe privée s'étend de AsyncTask nommée :AddDataAsyncTask

     
    private class AddDataAsyncTask extends  AsyncTask<Void, Void, Void> {
            @Override
            protected void onPreExecute() {
                Log.i("add", "onPreExecute");
                super.onPreExecute();
                // ici on va afficher notre progressDialog
               
            }
            @Override
            protected Void doInBackground(Void... params) {
                 Log.i("add", " start doInBackground");
                 /*ici on va faire notre requête de type POST
                  puisqu'on a des données à envoyer 
                  récupérer la réponse et extraire les données 
                  comme on dit précédemment , le réponse contient un               seul JSONObject avec un entier et String */ 
                 Log.i("add", " end doInBackground");
                return null;
            }
            @Override
            protected void onPostExecute(Void result) {
                Log.i("add", "onPostExecute");
                super.onPostExecute(result);
                /* ici on va cacher le progressDialog
                 et afficher un Toast selon le résultat */
              
            }
           
        }

    - Donc on obtient : (http://pastebin.com/Apv7KYMi : code total)

    
    private class AddDataAsyncTask extends  AsyncTask<Void, Void, Void> {
            @Override
            protected void onPreExecute() {
                Log.i("add", "onPreExecute");
                super.onPreExecute();
                progressDialog.show();
            }
           
            @Override
            protected Void doInBackground(Void... params) {
                Log.i("add", " start doInBackground");
                // Creating service handler class instance
                ServiceHandler sh = new ServiceHandler();
               
                List<NameValuePair> nameValuePair = new ArrayList<NameValuePair>(1);
               
                nameValuePair.add(new BasicNameValuePair("col2",col2Valeur.getText().toString()));
                nameValuePair.add(new BasicNameValuePair("col3",col3Valeur.getText().toString()));
                nameValuePair.add(new BasicNameValuePair("col4",col4Valeur.getText().toString()));
           
                // Making a request to url and getting response
                String jsonStr = sh.makeServiceCall(urlAdd, ServiceHandler.POST,nameValuePair); // type =POST
               
                Log.d("Response: ",jsonStr);
                if (jsonStr != null) {
                    try {
                       
                        JSONObject jsonObj = new JSONObject(jsonStr);
                        success = jsonObj.getInt("success");
                        message = jsonObj.getString("message");
                        Log.i("suucess", String.valueOf(success));
                        Log.i("message", message);
                       
                    } catch (JSONException e) {
                       
                        e.printStackTrace();
                    }
                }

                Log.i("add", " end doInBackground");
                return null;
            }
           
            @Override
            protected void onPostExecute(Void result) {
                Log.i("add", "onPostExecute");
                super.onPostExecute(result);
                if (progressDialog.isShowing())
                { progressDialog.dismiss();
                }
                if(success==1)
                {Toast.makeText(getApplicationContext(), "Good "+message, Toast.LENGTH_LONG).show();
                }
                else
                { Toast.makeText(getApplicationContext(), "Erreur" +message, Toast.LENGTH_LONG).show();
                }
            }
         }


    - & ainsi le résultat:
    logcat

        Remarque:
    • Si vous cliquez autre fois sur le bouton ajouter , votre application va se fermer (crasher) et l'erreur c'est que AddData est nulle .La solution est de changer la méthode onClick comme suit :
    public void onClick(View arg0) {
                    AddData =new AddDataAsyncTask();
                    AddData.execute();
              
                }

      2: ListDataActivity :Afficher les données de la base :

     - On ajoute une nouvelle activité à la projet  ListDataActivity dont on va lister les données insérées dans notre base en appelant le fichier affichage_bd.php.
      Rappelons qu'on a pas des données à envoyer ==>HTTP GET
    & pour la réponse , il diffère selon la valeur de success:
    • Si success = 0 :

    Donc on a dans ce cas un seul JSONObject qui contient un entier et un string.

    • Si success = 1 :



    Donc on a dans ce cas un seul JSONObject qui contient un entier (success=1) ,et JSONArray (tableau) dont les indices sont les col1, col2, col3 et col4 
    - Ainsi , dans l'asyncTask (doInBackground) , on va travailler sur la valeur success , on le récupere tout d’abord , & selon son valeur on décide de récupérer le reste des données
    - On va procéder comme la première activité. Smiley
    - Notre layout comporte qu'une listView: http://pastebin.com/u8SbFfT5
    - Pour faciliter le travail , notre listView sera simple non personnalisé (les données d'une ligne de la base seront affichées dans la même ligne ) . Si vous voulez personnaliser votre liste , jetez un œil  sur ce tutoriel : [Tutoriel] : ListView Android .
    • Variables globales
       
    CustomProgressDialog  progressDialog;
        String urlGet="http://192.168.1.*/enis_android_club/affichage_bd.php"; //adresse IP du PC
        GetDataAsyncTask getData;
        String message;
        int success;
        ListView lv;
        List<String> myListofData ;
        ArrayAdapter arrayadp;
     


    • OnCreate
       
    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_list_data);
            progressDialog = new CustomProgressDialog(this, R.drawable.loading_throbber);
            progressDialog.setCancelable(true);
            lv=(ListView)findViewById(R.id.listView1);
            myListofData = new ArrayList<String>();
            getData=new GetDataAsyncTask();
            getData.execute();   
        }
    • Notre asyncTask : GetDataAsynck (la même façon que la précédente):
      
    private class GetDataAsyncTask extends  AsyncTask<Void, Void, Void> {
            @Override
            protected void onPreExecute() {
                Log.i("add", "onPreExecute");
                super.onPreExecute();
                progressDialog.show();
            }
           
            @Override
            protected Void doInBackground(Void... params) {
                Log.i("add", " start doInBackground");
                ServiceHandler sh = new ServiceHandler();
               
                // Making a request to url and getting response
            String jsonStr = sh.makeServiceCall(urlGet, ServiceHandler.GET);

            Log.d("Response: ",jsonStr);
               
            if (jsonStr != null) {
            try {
                JSONObject jsonObj = new JSONObject(jsonStr);
                // return value of success
                success=jsonObj.getInt("success");
                Log.i("success", String.valueOf(success));
                if (success==0)
                {
                    // success=0 ==> there is a string = message
                    message=jsonObj.getString("message");
                    Log.i("message", message);
                }
                else if (success==1)
                {
                    // success=1 ==> there is an array of data = valeurs
                    JSONArray dataValues = jsonObj.getJSONArray("valeurs");
                    // loop each row in the array
                    for(int j=0;j<dataValues.length();j++)
                    {
                        JSONObject values = dataValues.getJSONObject(j);
                        // return values of col1 in valCol1
                        String valCol1= values.getString("col1");
                        // return values of col2 in valCol2
                        String valCol2= values.getString("col2");
                        String valCol3= values.getString("col3");
                        String valCol4= values.getString("col4");
                        //add a string witch contains all of data getted from the response
                        myListofData.add(valCol1+" - "+valCol2+" - "+valCol3+" - "+valCol4);
                        Log.i("Row "+(j+1), valCol1+" - "+valCol2+" - "+valCol3+" - "+valCol4);
                    }
                }
                         
            } catch (JSONException e) {
                e.printStackTrace();
            }
            } else {
                Log.e("ServiceHandler", "Couldn't get any data from the url");
            }

            Log.i("add", " end doInBackground");
            return null;
            }
           
            @Override
            protected void onPostExecute(Void result) {
                Log.i("add", "onPostExecute");
                super.onPostExecute(result);
                if (progressDialog.isShowing())
                {
                    progressDialog.dismiss();
                }
                if(success==1)
                {
                    Toast.makeText(getApplicationContext(), "Bien récues ", Toast.LENGTH_LONG).show();
                    // show the list view contains the data
                    arrayadp=new ArrayAdapter(getApplicationContext(),  android.R.layout.simple_list_item_1, myListofData);                                   
                    lv.setAdapter(arrayadp); 
                }
                else
                {
                    Toast.makeText(getApplicationContext(), "Erreur", Toast.LENGTH_LONG).show();
                }
              }  
        }

    Remarques:
    • J'ai ajouté un autre bouton dans la 1er activité qui permet d'afficher la 2eme (ListDatActivity)
    • N'oublier pas de vérifier que la deuxième activité est ajoutée dans le manifest.xml
    • Vous pouvez également changer l'adaptateur de la listeView.
    • L'asynckTask est exécuté dés que l'activité est crée.
    • Il faut être attentif à la format JSON du réponse: JSONObject , JSONArray (array de données)
      exemple d'affichage d'une ligne de la liste
    Voici le code total de la deuxième activité http://pastebin.com/qUEeT8AJ

         3: EditSuppActivity :Editer / Supprimer une donnée de la base :
     

    • Pour le moment , nous avons développés deux activités qui contiennent  deux asyncktask (GET & POST) avec différents réponses. 
    • Cette activité (EditSuppActivity) à pour but de modifier ou supprimer une donnée choisit par utilisateur en cliquant sur une ligne de listeView. 
    • Pour moi , j'ai simplifié la liste en mettant tout les données dans la même ligne , donc en cliquant sur une ligne , je vais extraire les données chacune dans une variable et les envoient vers cette activité en utilisant intent 
    •  On modifie l'activité ListDataActivity  en ajoutant dans la méthode onCreate:
    lv.setOnItemClickListener(new OnItemClickListener() {         
                public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3)
                {
                    // s= value of seleted row
                    String s=(String) (lv.getItemAtPosition(arg2));
                    // on each row , I have save all of data separted by '-' : col1-col2-col3-col4
                    String[] patrs = s.split(" - ");
                    //parts[0] contains value of col1 , parts [1] contains value of col2 of each row
                    Intent intent = new Intent(ListDataActivity.this, EditSuppActivity.class);
                    //send data to the next activity
                    intent.putExtra("col1Value", patrs[0]);
                    intent.putExtra("col2Value", patrs[1]);
                    intent.putExtra("col3Value", patrs[2]);
                    intent.putExtra("col4Value", patrs[3]);
                    startActivityForResult(intent, 100);
                    finish();            }                                                             
            }); 
    • Aprés avoir passer les données , on va les mettre dans des EditText  inchangées (setEnabled(false)).
    • On crée 3 Boutons : 
    1. Editer : pour activer la modification 
    2. Enregistrer : pour sauvegarder les données (Mise à jour dans la base)
    3. Supprimer :pour supprimer les données de la base. 
    4. Code xml du layout activity_edit_supp.xml : http://pastebin.com/RR17Uz5j
     
    activity_edit_supp.xml
    • Bon le démarche est le suivant :
    1. Si l'utilisateur clique sur Supprimer (les autres sont desactivés) : les données seront supprimées de la base ==> AsyncTask
    2. S'il clique sur Editer , les champs seront activées ainsi le bouton Enregistrer .
    3. C'est un jeu de boutonsSmiley
    A- SuppDataAsyncTask : AsyncTask pour supprimer la ligne sélectionnée:
     C'est presque comme les classes précédents !

     ==> On doit envoyer une valeur (paramètre : col1) ==> HTTP POST
    Pour le retour de la requête:
    ==> Un JSONObject avec un entier et String
     
    • On déclare les variables globales nécessaires : urlSupp(url vers le fichier suppression_bd.php), success , message , progressDialog:
     
    supp.setOnClickListener(new OnClickListener() {
              
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    new SuppDataAsyncTask().execute();
                }
            });

    • & pour l'asyncTask :
    private class SuppDataAsyncTask extends  AsyncTask<Void, Void, Void> {
            @Override
            protected void onPreExecute() {
                Log.i("supp", "onPreExecute");
                super.onPreExecute();
                progressDialog.show();
            }
           
            @Override
            protected Void doInBackground(Void... params) {
                Log.i("supp", " start doInBackground");
                ServiceHandler sh = new ServiceHandler();
                List<NameValuePair> nameValuePair = new ArrayList<NameValuePair>(1);
                nameValuePair.add(new BasicNameValuePair("col1",valCOL1.getText().toString()));
                // Making a request to url and getting response
                String jsonStr = sh.makeServiceCall(urlSupp, ServiceHandler.POST,nameValuePair);   
               
            Log.d("Response: ",jsonStr);
            if (jsonStr != null) {
            try {
                       
                JSONObject jsonObj = new JSONObject(jsonStr);
                // return value of success
                success=jsonObj.getInt("success");
                message = jsonObj.getString("message");
                Log.i("suucess", String.valueOf(success));
                Log.i("message", message);
                         
            } catch (JSONException e) {
                e.printStackTrace();
            }
            } else {
                Log.e("ServiceHandler", "Couldn't get any data from the url");
            }

            Log.i("supp", " end doInBackground");
            return null;
            }
           
            @Override
            protected void onPostExecute(Void result) {
                Log.i("supp", "onPostExecute");
                super.onPostExecute(result);
                if (progressDialog.isShowing())
                {
                    progressDialog.dismiss();
                }
                if(success==1)
                {
                    Toast.makeText(getApplicationContext(), "Supprimé ", Toast.LENGTH_LONG).show();
                }
                else
                {
                    Toast.makeText(getApplicationContext(), "Erreur", Toast.LENGTH_LONG).show();
                }
               Intent intent = new Intent(EditSuppActivity.this, ListDataActivity.class);
                startActivityForResult(intent, 100);
                finish();
            }
        }
    B- UpdateDataAsyncTask : AsyncTask pour mise à jour la ligne sélectionnée:
      Cette asyncTask est similaire à l'asyncTask d'jout de données (mais dans la requéte en ajoute la valeur de col1) Smiley 

    Je pense que vous avez appris le principe :D

    Partie 3 :  Conclusion:
    Aprés ces deux parties , j'espére que vous avez appris une leçon trés important dans la vie professionelle (PFE , étude etc. ) .
    Il y a beaucoup des notions , mais ce sont facile à comprendre , surtout que ce tutoriel est générale et vous pouvez l'utiliser tel qu'il est en changants les champs (col1= id , col2= prix etc.. )
    Je vous souhaite bonne chance , et s'il y a une question , n'hesitez pas de nous contacter et poser vos questions soit par email (enisandroidclub@gmail.com) soit par un commentaire.
    En fin je vous donne le lien de téléchargemnt du code source total: lien ou l'explorer sur le github https://github.com/ziedrebhi/AndroidMySQLTuto

    Smiley


    Desolé pour les fautes d'orthographes

    11 commentaires:

    1. Excellent article :).. Pour le code c'est mieux de le mettere sur github.
      Bonne continuation.

      RépondreSupprimer
      Réponses
      1. Thanx ! :) Malheureusement bogger ne fournit pas un outil pour bien présenter le code , je vais le changer et mettre le code sur gihub .

        Supprimer
    2. mon application s’arrête quand je clique sur ajouter !? est qu'il y a des solution possible ?

      RépondreSupprimer
    3. A chaque fois j'ai cette erreur: "erreurnull". Que faire?

      RépondreSupprimer
    4. Ce commentaire a été supprimé par l'auteur.

      RépondreSupprimer
    5. Bonjour,
      J'obtiens aussi "erreurnull", quelle adresse ip faut-il mettre en url ?
      j'utilise "http://10.0.0.2:80/enis_android_club/ajout_bd.php"
      Sachant que mon fichier ajout_bd.php se trouve dans www/enis_android_club/

      RépondreSupprimer
      Réponses
      1. essayer de démarrer votre scripte php directement par votre navigateur
        http://10.0.0.2/www/enis_android_club/ajout_bd.php
        votre adresse ip dans le réseau sachant que c est 10.0.0.2 suivi par le lien du script

        Supprimer
    6. Bonjour, merci bcp pour le tuto
      J'obtiens aussi "erreurnull",probleme de conversion.
      Java.lang.String cannot be converted to JSON Object
      SVP c'est quoi le probleme???

      RépondreSupprimer
    7. Super tuto ! Merci je cherchais justement à créer une appli bibliothèque virtuelle : parfait

      RépondreSupprimer
    8. Bonsoir merci beaucoup pour le tuto.
      Par contre j'ai un blocage au niveau de l'Url.
      Mon adresse ip est 192.168.0.1
      Donc pour atteindre le fichier l'Url est = 192.168.0.1/nomDossier/ajout_bd.php
      alors ceci m'indique
      Forbidden

      You don't have permission to access /mapharmacieBD/ajout_bd.php on this server.

      Aidez moi SVP si quelque en connait à présent je tourne en rond sur le net.
      C'est URGENT !!!!!

      RépondreSupprimer
    9. Bonsoir a vous. En utilisant le code on me dit de creer une classe ServiceHandler. Comment je peux avoir la cette classe?
      Merci

      RépondreSupprimer

    Sauvegarder le lien

    Twitter Delicious Facebook Digg Stumbleupon Favorites More

    Recherche

    Pages vues

    Articles populaires

    Notre page facebook

    Libellés

    Tutoriel (17) Android (12) Activite (10) Actualite (6) Event (5) 2014 (3) Compétition (2) MySQL (2) json (2) php (2) webservices (2) APK (1) Design (1) Formation (1) KitKat (1) SIB (1) Securité (1) sfax (1)
    Copyright © ENIS Android Club