Editer
(vieilles notes dépréciées)

Android


La majorité des applications Android sont écrites en Java. Elles ne fonctionnent pas avec la machine virtuelle d'oracle : soit elles utilisent la machine virtuel Dalvik (avant android 5), soit elles sont recompilés pour devenir du code natif Android. Ca reste du Java, tout ce qu'il est possible de faire en JavaSE se retrouve sur android. Par contre l'interface graphique n'a rien à voir (plus de Swing)et autre grosse différence, on doit gérer le multitâche lors du développement. Android a également ses propres règles de sécurité. Il faut également tenir compte des caractéristiques propres à un appareil mobile comme le mode d'affichage (portrait/paysage), la taille de l'écran variable,lesoutils comme le gyroscope, la caméra, etc. C'est le multitâche qui reste le plus dur à comprendre et mettre en place.

Dans un projet Android, on trouve un dossier "src" avec les packages java (doivent être nommés mot1.mot2). Le dossier "Gen" (invisible sous Android Studio) contient lui tous les fichiers générés (on y touche pas), on y trouvera la class "R" qui est généré à chaque compilation. Le dossier "Manifest" contient "Android.Manifest", un fichier où on indiquera les classes de l'application, son titre, le type de SDK utilisé, les thèmes et autres réglages.
Dans "res", on trouvera plusieurs choses importantes. D'abord, les ressources images ("drawable") avec des dossiers pour les différentes tailles d'images selon la résolution de l'appareil sur lequel elles seront affichées. Ensuite, le dossier "layout" qui contient les vues (en xml). Enfin, le dossier "values" contenant des XML où sont renseignés les constantes (par exemple les textes affichés sur les boutons).
La liaison entre les vues en xml et le code java est faite avec la classe "R" dans le dossier "gen", une référence vers chaque élément ajouté dans le dossier RES sera ajouté automatiquement à cette classe R pour qu'on puisse y accéder par la suite (par exemple, pour afficher une vue, on fera setContentView(R.layout.ma_jolie_vue);, pour trouver un boutton déclaré dans le xml d'une vue, ce sera findViewById(R.id.MonJoliBouton);).

Le contrôleur est une "activité", il hérite de la classe "activity" (et ces activités devront être déclarées dans Android.Manifest). On redéfinira la classe "onCreate" pour indiquer ce qui se passera au lancement de l'activité (affichage d'une vue, initialisation des variables, création des listeners...). Il y a plusieurs activités pour une appli, quand on passe de l'une à l'autre, l'activité est mise en pause par défaut (mais on pourrait la laisser tourner en arrière plan en redéfinissant des méthodes). Tous les états possibles d'une application sont des méthodes d'activity : onCreate, onStart, onRestart, onResume,onPause, onStop, onDestroy (il faut la stopper avant). Il suffit de faire super.onPause() pour mettre en pause. En cas de long chargement (grosse BDD par exemple), on pourra avoir besoin de redéfinir ces états pour éviter d'interrompre le chargement.

Pour les interactions, il y a 2 façons de faire :
-soit du côté vue, dans le xml, en mettant un onClick sur un bouton :

<Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Cliquez-moi" android:onClick="MaSuperbeMethode" />

C'est très simple mais la vue n'est pas réutilisable et on n'est plus vraiment dans un modèle MVC.

-soit du côté contrôleur, en ajoutant un listener sur un bouton.

Exemple du cours :

//Un listener pour table View.OnClickListener clickButtonTable = new View.OnClickListener() { @Override public void onClick(View actuelView) { // traitement déclenché par le clic sur le bouton Intent intent = new Intent(MainActivity.this, TableConversions.class); startActivity(intent); } }; //on l'attribue au bouton table buttonTable.setOnClickListener(clickButtonTable);


Exemple plus simple sur la doc android :

button = (Button) findViewById(R.id.button_id); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Perform action on click } });


Ca respecte mieux le modèle MVC car la vue est indépendante du contrôleur par contre le code est moins lisible (utilisation de classes anonymes).

On va potentiellement avoir beaucoup de code dans la classe anonyme si on a plusieurs boutons dans la vue. Si on est gêné par ces classes anonymes, on peut faire une classe à part avec tous les listeners dedans.

Fragments


Un fragment est un peu l'équivalent des panels en swing, c'est à dire un système de container qui contient des élements d'interface (boutons, listViews, images, etc). Ils pourront avoir les mêmes layout que les activités (en xml). On peut disposer les fragments un peu comme en veut de façon avec différents types de périphériques. Comme une activité, un fragment possède un cycle de vie.
2 manières de faire des fragments, de façon statique (en dur, avec une référence vers la classe de notre fragment dans le xml de l'activité, facile à mettre en place mais on ne pourra pas retirer le fragment plus tard lors du cycle de vie de l'appli) ou dynamique (en code java, on peut remplacer les fragments en temps réel).
Les Fragments ont un cycle de vie différent des applications cf. L'activity qui a appelé le fragment est passée en paramètre lors du onAttach. On peut la récupérer avec un getActivity.

Pour les fragments dynamiques, ils ne sont pas ajoutés manuellement au "backstack" (la liste d'applis quand on appuie sur "back"), il faut le demander explicitement avec la méthode "addToBackstack".




Exemple minimum de fragment statique (associé à un "fragment_chrono.xml") :

package com.example.aka.exocdi_chronometre; import android.app.Fragment; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class FragmentChrono extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate le layout (càd récupère le XML et l'utilise comme vue) return inflater.inflate(R.layout.fragment_chrono, container, false); } }


Pour utiliser ce fragment dans la vue (si le chemin vers la classe, ici "com.example.aka.exocdi_chronometre.FragmentChrono" n'est pas trouvé dans android studio, l'écrire puis faire alt+enter pour qu'il créé et reconnaisse la classe fragment automatiquement) :

<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.aka.exocdi_chronometre.FragmentChrono" android:id="@+id/headlines_fragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>


En dynamique, les fragments seront appelés depuis le code java.

Ci-dessous un exemple fonctionnel tiré de la doc android :

package com.example.aka.exocdi_chronometre; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends FragmentActivity { //attention, si on extends FragmentActivity, il faudra "import android.support.v4.app.Fragment;" dans la classe du fragment @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Check that the activity is using the layout version with // the fragment_container FrameLayout if (findViewById(R.id.layoutQuiContientLeFragment) != null) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. if (savedInstanceState != null) { return; } // Create a new Fragment to be placed in the activity layout FragmentChrono fragmentChrono = new FragmentChrono(); // In case this activity was started with special instructions from an // Intent, pass the Intent's extras to the fragment as arguments fragmentChrono.setArguments(getIntent().getExtras()); // Add the fragment to the 'fragment_container' FrameLayout getSupportFragmentManager().beginTransaction() .add(R.id.layoutQuiContientLeFragment, fragmentChrono).commit(); } }


Et le fragment :

package com.example.aka.exocdi_chronometre; import android.support.v4.app.Fragment; //ATTENTION, l'import a changé comparé au fragment statique import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class FragmentChrono extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_chrono, container, false); } }


Pour changer de fragment, on peut en ajouter un devant, ou cacher l'ancien avec "hide", ou utiliser replace (attention replace stoppe l'ancien fragment.
Il faut passer par un objet fragmentTransaction car plusieurs fragments ont le risque d'être manipulés par plusieurs objets à la fois.


FragmentHistorique fragmentHistorique = new FragmentHistorique(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // On ajoute le fragment appelé "fragmentHistorique" devant l'ancien fragment transaction.add(R.id.layoutQuiContientLeFragment, fragmentHistorique); //ajoute la transaction au "backstack" -ce qui nous permet de retrouver le précédent fragment avec back transaction.addToBackStack(null); //on peut donner un nom à cet ajout au backstack pour le récupérer plus tard d'une autre façon qu'en appuyant sur back //Effectue la transaction transaction.commit();




Installation sur Linux



Dans tous les cas, il faudra installer le SDK Android. Il peut être téléchargé tout en bas de la page https://developer.android.com/studio/index.html (lien direct vers la version 24.4.1).

(Ce processus d'installation n'a été testé qu'une seule fois étalé sur plusieurs mois : ) Une fois téléchargé, direction le dossier "tools", lancez une console et exécuter ./android update sdk. Installer Android SDK Tools, Android SDK Platform-tools, AndroiD SDK Build-tools + les éléments selon votre la version d'android avec laquelle vous voulez travailler (tout le dossier "Android 7.0 API 24" par exemple).

Pour créer un téléphone émulé, lancer ./android avd.

Netbeans


Il faut utiliser le plugin NBAndroid avec Netbeans 8.1 (la version de novembre 2015 n'a pas fonctionné sur 8.2).

Instructions d'installation.

Dans Netbeans, tools plugin, setting, add "http://nbandroid.org/release81/updates/updates.xml". Puis available plugin, cocher Android, redémarrer netbeans. Puis tools, options, miscellaneous, renseigner le dossier du SDK android. Créer un nouveau projet Android (le nom du package doit être sous la formemot1.mot2). Au démarrage, vous serez invité à choisir un de vos "AVD".

Mieux vaut tout de même utiliser Android Studio.

OrmLite


Un ORM (Object Relational Mapping) léger, utilisé pour fournir un accès à une base de donnée pour une application.

A la différence de JPA, il ne gère pas les transactions, il n'y a pas de gestion de la concurrence (vu qu'il n'y a qu'une seule application qui utilise la base), il n'y a pas de fichier de config -tout se fait depuis le code java, il est "model driven" (=on écrit les models PUIS la base et les tables seront générés automatiquement alors que pour JPA on créé les tables puis on importait depuis MySQL).
Cette approche "model driven" est possible aussi en JPA ("generate tables from entities") mais n'est pas faite en général.

Comme avec JPA, on aura du code java (de type monDao.queryBuilder().where().eq("prenom",prenom).query();, interprété par l'ORM, qu'il transformera en requête SQL. Les deux travaillent avec des annotations :

@DatabaseField(id = true private String name; @DatabaseField(columnName = "passwd", canBeNull = false) private String password;


On ne peut pas avoir de clef concaténée en OrmLite, il faut la créer soit même par concaténation, dans le constructeur surchargé et le setter : clef1Clef2=clef1.toString()+clef2.toString();


Il faudra importer les packages OrmLite dans le projet.

Exemples



Partie serveur d'un client/serveur :

package com.monpackage; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.*; public class Main { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(8821); Socket s = ss.accept(); DataInputStream dis=new DataInputStream(s.getInputStream()); String str=(String)dis.readUTF(); System.out.println("Message="+str); ss.close(); System.out.println("Bye."); }catch(Exception e) { System.out.println(e.getMessage()); } } }


Partie client d'un client/serveur :

package com.monpackage; import java.io.*; import java.net.Socket; public class Main { public static void main(String[] args) { try{ Socket s=new Socket("localhost",8821); DataOutputStream dout=new DataOutputStream(s.getOutputStream()); dout.writeUTF("Hello Server"); dout.flush(); dout.close(); s.close(); }catch(Exception e){System.out.println(e);} } }


Ecrire dans un fichier :

try { PrintWriter sortie = new PrintWriter(new FileOutputStream("Etudiant.txt")); sortie.print(prenomEtudiant); sortie.print(nomEtudiant); sortie.print(note); sortie.close (); } catch (Exception e) { System.out.println(e.getMessage()); }


Exemple d'application Android (convertisseur euros/francs):

package afpa.cdi.euroFrancs; import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private EditText francsEditText; private EditText eurosEditText; /** Méthode appelée lors de la création de l'activité. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //on indique qu'on utilise ressources/values/strings.xml setContentView(R.layout.main); // On récupère les EditText pour les modifier plus tard francsEditText = (EditText) findViewById(R.id.francsEditText); eurosEditText = (EditText) findViewById(R.id.eurosEditText); // On créé un listener qui va surveiller les changements sur le champ du texte TextWatcher surveilleModifTexteEuros = new TextWatcher() //une classe anonyme avec ses méthodes obligatoires { public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { //on converti l'autre champ if(eurosEditText.hasFocus()){ if(!eurosEditText.getText().toString().equals("") && !eurosEditText.getText().toString().equals(".")){ Long nombreConverti=Math.round(Double.valueOf(eurosEditText.getText().toString())*655); francsEditText.setText(String.valueOf(nombreConverti.doubleValue()/100)); }else{ //si le champ euros est complètement effacé, il faut aussi effacer le champ francs francsEditText.setText(""); } } } public void afterTextChanged(Editable s) { } }; TextWatcher surveilleModifTexteFrancs = new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { if(francsEditText.hasFocus()){ if(!francsEditText.getText().toString().equals("") && !francsEditText.getText().toString().equals(".")){ Long nombreConverti=Math.round(Double.valueOf(francsEditText.getText().toString())*655); eurosEditText.setText(String.valueOf(nombreConverti.doubleValue()/100)); }else{ //si le champ francs est complètement effacé, il faut aussi effacer le champ euros eurosEditText.setText(""); } } } public void afterTextChanged(Editable s) { } }; // On lui attribue une méthode si il reçoit le focus eurosEditText.addTextChangedListener(surveilleModifTexteEuros); francsEditText.addTextChangedListener(surveilleModifTexteFrancs); //BOUTON RESET // On implémente l’interface OnClickListener OnClickListener clickButton = new OnClickListener() { @Override public void onClick(View actuelView) { // traitement déclenché par le clic sur le bouton // par exemple affichage d’une boite popup Toast.makeText(getBaseContext(), "Reset", Toast.LENGTH_LONG).show(); francsEditText.setText(""); eurosEditText.setText(""); } }; // On récupère le bouton cliqué Button bouton = (Button) findViewById(R.id.boutonReset); // On appelle sa méthode setOnClickListener() bouton.setOnClickListener(clickButton); } }


Le layout main.xml :

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/euros" /> <EditText android:id="@+id/eurosEditText" android:layout_width="fill_parent" android:layout_height="40dip" android:ems="20" android:layout_marginLeft="36dip" android:inputType="numberDecimal" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/francs" /> <EditText android:id="@+id/francsEditText" android:layout_width="fill_parent" android:layout_height="40dip" android:ems="20" android:layout_marginLeft="36dip" android:inputType="numberDecimal" /> <Button android:id="@+id/boutonReset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/reset" /> </LinearLayout>


Les activités sont des classes qui hérite d'Activity, elle doivent être déclarés dans AndroidManifest.xml.

Pour passer d'une activité à une autre, on utilise des intents :

//cecodepeut être dans un OnClickListener par exemple Intent intent = new Intent(MainActivity.this, Resultat.class); intent.putExtra("nomPourRecupererObjetEnvoye",objetEnvoye); //on peut envoyer des paramètres de la façon suivante startActivity(intent);


Pour récupérer des objets envoyés d'une activité à l'autre, on peut écrire dans l'activité de destination : getIntent().getExtras().getString("nomPourRecupererObjetEnvoye");

Certains objets doivent être alimentés par des "adapters", qui sont l'équivalent des DefaultListModel en swing. Ces adapters vont faire l'intermédiaire entre un tableau et un objet (une liste, une combobox) qui utilisera ce tableau. Lorsque le tableau sera mis à jour, l'adapter mettra à jour l'objet dynamiquement.

Ci-dessous, un adapter pour un spinner :
ArrayAdapter<String> adaptateurPourMonObjet = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, monTableau);


Un adapteur pour une listeView :

super.onCreate(savedInstanceState); setContentView(R.layout.activite_principale); //la liste de choses qui seront affichées dans la liste view listTextes=new ArrayList(); //on récupère la liste view maListView = (ListView) findViewById(R.id.maListeView); //on ajoute les éléments voulus listTextes.add("texte1"); listTextes.add("texte2"); listTextes.add("texte3"); //il faut utiliser un adapter final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listTextes); //on met l'adapter sur la listeview maListView.setAdapter(adapter);


On ne peut pas avoir de tableau à 2 dimensions dans le XML.

On pourrait faire ([http://stackoverflow.com/questions/6571477/is-it-possible-to-access-a-string-array-item-in-android|exemple]) :

<array name="Unite1"> <item name="nom">Euros</item> <item name="valeurEnEuro">1</item> </array> <array name="Unite2"> <item name="nom">Francs</item> <item name="valeurEnEuro">0.1524</item> </array> <array name="unitesUtilisees"> <item>@array/Unite1</item> <item>@array/Unite2</item> </array>


Puis :

//on récupère le contenu du tableau listant les taux et les unites TypedArray unitesUtilisees = getResources().obtainTypedArray(R.array.unitesUtilisees); tabNomUnites = new String[unitesUtilisees.length()]; tabTaux = new String[unitesUtilisees.length()]; for (int i = 0; i < unitesUtilisees.length(); ++i) { //on récupère l'id de la ressource stockée à la position i du tableau unitesUtilisees //c'est à dire un tableau de string System.out.println("contenu de unitesUtilisees ?"+String.valueOf(getResources().obtainTypedArray(unitesUtilisees.getResourceId(i, 0)))); System.out.println("Quelle unite ?"+String.valueOf(getResources().getResourceEntryName(unitesUtilisees.getResourceId(i, 0)))); TypedArray soustabUnitesUtilisees=getResources().obtainTypedArray(unitesUtilisees.getResourceId(i, 0)); System.out.println("Le contenu de soustabUnitesUtilisees :"+soustabUnitesUtilisees.toString()); //on parcourt le tableau qui vient d'être renvoyé for (int j = 0; j < soustabUnitesUtilisees.length(); ++j) { System.out.println("Nom du champ :"+soustabUnitesUtilisees.getResourceId(j,99)); System.out.println("Contenu du champ :"+String.valueOf((soustabUnitesUtilisees.getString(j)))); } }


Renvoie

I/System.out: contenu de unitesUtilisees ?[3, 5, 2, 0, 0, 0, 16, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0] I/System.out: Quelle unite ?Unite1 I/System.out: Le contenu de soustabUnitesUtilisees :[3, 5, 2, 0, 0, 0, 16, 1, 2, 0, 0, 0] I/System.out: Nom du champ :99 I/System.out: Contenu du champ :Euros I/System.out: Nom du champ :99 I/System.out: Contenu du champ :1


MAIS impossible de récupérer l'attribu "name" dans <item name="nom">Euros</item>. On est donc forcé de respecter un sens (nom de la devise en premier, nom de la valeur en bas).

Donc :

<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="Unite1"> <item>Euros</item> <item>1</item> </string-array> <string-array name="Unite2"> <item>Francs</item> <item>0.1524</item> </string-array> <array name="unitesUtilisees"> <item>@array/Unite1</item> <item>@array/Unite2</item> </array> </resources>


TypedArray unitesUtilisees = getResources().obtainTypedArray(R.array.unitesUtilisees); tabNomUnites = new String[unitesUtilisees.length()]; tabTaux = new String[unitesUtilisees.length()]; for (int i = 0; i < unitesUtilisees.length(); ++i) { tabNomUnites[i]=getResources().getStringArray(unitesUtilisees.getResourceId(i, 0))[0]; tabTaux[i]=getResources().getStringArray(unitesUtilisees.getResourceId(i, 0))[1]; }


Un exemple de Handler ([https://guides.codepath.com/android/Repeating-Periodic-Tasks#scheduledthreadpoolexecutor|bon tuto]) :

package com.example.aka.exocdi_chronometre; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class HandlerThreadChrono extends Handler { private MainActivity mainActivity; public HandlerThreadChrono(MainActivity ma) { mainActivity=ma; } @Override public void handleMessage(Message msg) { mainActivity.mettreAJourChrono(); } }


Un exemple de thread qui communique avec le handler plus haut :

package com.example.aka.exocdi_chronometre; import android.os.Bundle; import android.os.Message; public class ThreadChrono implements Runnable { private HandlerThreadChrono handlerThreadChrono; private int i; public ThreadChrono(HandlerThreadChrono h) { handlerThreadChrono=h; } public void run(){ //on contacte le chrono pour lui dire d'updater la vue handlerThreadChrono.sendEmptyMessage(0); // System.out.println("ThreadChrono, tour "+(i++)); //on relance dans xxx millisecondes handlerThreadChrono.postDelayed(this,100); } }


Pour faire fonctionner les thèmes dans Android Studio, changer la valeur de theme dans le manifest par android:theme="@style/Theme.AppCompat.

Pager


Le système utilisé en android pour faire défiler des fragments en glissant avec le doigt. Il n'y a pas besoin de fragmentManager si on utilise un pager.

Layout du pager :

<?xml version="1.0" encoding="utf-8"?> <!-- activity_screen_slide.xml --> <android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/idDeMonPager" android:layout_width="match_parent" android:layout_height="match_parent" />


Il faudra une classe pour le pager, héritant de FragmentStatePagerAdapter (ou PagerAdapter selon les cas) :

import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; public class MaClassePager extends FragmentStatePagerAdapter { private FragmentChrono fragmentChrono; private FragmentHistorique fragmentHistorique; public MaClassePager(FragmentManager fm) { super(fm); System.out.println("Constructeur du fragment"); fragmentChrono=new FragmentChrono(); fragmentHistorique=new FragmentHistorique(); } @Override public Fragment getItem(int position) { //à la position X, on retourne un fragment ou un autre System.out.println("getItem dans le pager"); if(position==0) { System.out.println("On retourne"+fragmentChrono); return fragmentChrono; } return fragmentHistorique; } @Override public int getCount() { return 2; //le nombre de pages dans le fragment } @Override public void notifyDataSetChanged() { //cette méthode est appelée quand on veut rafraichir les fragments super.notifyDataSetChanged(); System.out.println("NotifyDatasetchanged dans le pager"); fragmentHistorique.actualiserHistorique(); } }


Puis dans le MainActivity :

private ViewPager viewPager; private MaClassePager monInstanceDepager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_du_pager);//on utilise le layout du pager viewPager=(ViewPager) findViewById(R.id.idDeMonPager); monInstanceDepager=new MaClassePager(getSupportFragmentManager()); viewPager.setAdapter(monInstanceDepager);



Autre exemple simplifié à l'extrême et fonctionnel :
La class "fragment 1" :

package com.example.aka.testpager; import android.support.v4.app.Fragment; //ATTENTION, l'import a changé comparé au fragment statique import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class Fragment1 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment1, container, false); } }


La classe fragment 2 est identique.

Le pager :

package com.example.aka.testpager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; public class MaClassePager extends FragmentStatePagerAdapter { public MaClassePager(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { //à la position X, on retourne un fragment ou un autre System.out.println("Position : "+position); switch (position) { case 0: return new Fragment1(); case 1: return new Fragment2(); default: return new Fragment1(); //il ne faut pas retourner null sinon crash } } @Override public int getCount() { return 2; //le nombre de pages dans le fragment } @Override public void notifyDataSetChanged() { //cette méthode est appelée quand on veut rafraichir les fragments super.notifyDataSetChanged(); } }


La mainActivity :

package com.example.aka.testpager; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { private ViewPager viewPager; private MaClassePager monInstanceDepager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_du_pager); viewPager=(ViewPager) findViewById(R.id.idDeMonPager); monInstanceDepager=new MaClassePager(getSupportFragmentManager()); viewPager.setAdapter(monInstanceDepager); } }


Web


Pour donner l'autorisation à l'application d'aller sur internet, dans le manifest, juste avant l'ouverture de la balise "application", ajouter <uses-permission android:name="android.permission.INTERNET" />.

Application utilisant Apache httpclient


Pour utiliser les librairies apache, mettre dans le fichier build.gradle useLibrary 'org.apache.http.legacy' :

android { ... buildTypes { ... } ... useLibrary 'org.apache.http.legacy' }

Il faudra ensuite utiliser un Thread pour les connexions (si on se connecte à internet dans la classe "main", on risque d'obtenir une erreur "NetworkOnMainThreadException").

Ci-dessous 2 classes d'une application fonctionnelle (si on veut que le thread communique avec le min, il faudra rajouter un handler) :

La classe main, qui lance le thread :

package com.example.aka.testrest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread t=new Thread(new ThreadConnexion()); t.start(); } }



La classe ThreadConnexion qui se connecte au serveur et récupère les infos :

package com.example.aka.testrest; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; public class ThreadConnexion implements Runnable { @Override public void run() { System.out.println("Le threadConnexion run..."); DefaultHttpClient httpclient = new DefaultHttpClient(); try { HttpHost target = new HttpHost("192.168.122.1", 8080, "http"); HttpGet getRequest = new HttpGet("/testWebServiceAndroid"); System.out.println("On se connecte à " + target); HttpResponse httpResponse = httpclient.execute(target, getRequest); HttpEntity entity = httpResponse.getEntity(); System.out.println("----------------------------------------"); System.out.println(httpResponse.getStatusLine()); System.out.println("----------------------------------------"); if (entity != null) { System.out.println(EntityUtils.toString(entity)); } } catch (Exception e) { e.printStackTrace(); } finally { httpclient.getConnectionManager().shutdown(); } } }


Rest


Exemple d'une application REST avec Android. Attention, cette façon de faire est dépréciée depuis le sdk 21.

Du côté serveur :
Créer la base de donnée, créer le projet web java EE, ajoutez une connexion vers votre base, puis ajouter unpackage modele dans le projet (on pourra le supprimer plus tard, il servira juste à générer le Rest), clic droit new "entity classes from database" , ajouter la connexion vers votre base à la liste, générer les classes, puis clic droit sur le projet, new "restful web service from entities classes".

Du côté client (android) :
Tout d'abord, indiquer dans le fichier gradle les librairies que l'on souhaite utiliser (ici gson et org.apache.http.legacy) :

android { ... } buildTypes { ... } useLibrary 'org.apache.http.legacy' } dependencies { ... compile 'com.google.code.gson:gson:2.4' ... }



Ensuite, il faudra l faudra instancier le thread dans la classe main :

package com.example.aka.testrest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread t=new Thread(new ThreadConnexion()); t.start(); } }


Pour l'exemple, on va récupérer un objet de type Etudiant, il nous faut donc une classe Etudiant pour que GSON puisse le construire à partir des infos reçues du serveur :

package com.example.aka.testrest; public class Etudiant { private int id; private String nom; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } }


Enfin, le thread qui va se connecter :

package com.example.aka.testrest; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ThreadConnexion implements Runnable { @Override public void run() { System.out.println("Le threadConnexion run..."); DefaultHttpClient httpclient = new DefaultHttpClient(); try { HttpHost target = new HttpHost("192.168.122.1", 8080, "http"); HttpGet getRequest = new HttpGet("/testWebServiceAndroid/webresources/modele.etudiant"); System.out.println("On se connecte à " + target); HttpResponse httpResponse = httpclient.execute(target, getRequest); HttpEntity entity = httpResponse.getEntity(); InputStream content = null; try { content = entity.getContent(); //passage du contenu dans inputStream } catch (IOException ex) { } catch (UnsupportedOperationException ex) { } Reader reader = new InputStreamReader(content); //objet de type reader pour reconstruire l'objet GsonBuilder gsonBuilder = new GsonBuilder(); //objet factory à objet json Gson gson = gsonBuilder.create(); //creation du objet gson List listeEtudiants = new ArrayList<Etudiant>(); listeEtudiants = Arrays.asList(gson.fromJson(reader, Etudiant[].class)); //gson fromJson resconstruit des objets à partir d'un inputstream et des métadonées de type Class try { content.close(); //finalisation on ferme le flux } catch (IOException ex) { } System.out.println("Mon étudiant joli :"+((Etudiant)listeEtudiants.get(0)).getNom()); } catch (Exception e) { e.printStackTrace(); } finally { httpclient.getConnectionManager().shutdown(); } } }


Résultat:

I/System.out: Le threadConnexion run... I/System.out: On se connecte à http://192.168.122.1:8080 D/NetworkSecurityConfig: No Network Security Config specified, using platform default I/OpenGLRenderer: Initialized EGL, version 1.4 D/OpenGLRenderer: Swap behavior 1 E/EGL_emulation: tid 19823: eglSurfaceAttrib(1174): error 0x3009 (EGL_BAD_MATCH) W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0xa24d0840, error=EGL_BAD_MATCH I/System.out: !!!!!!!!!!!!!! Content :org.apache.http.conn.EofSensorInputStream@860a409 I/System.out: Mon étudiant joli :Bob



Autre exemple qui n'est pas deprecated au moment d'écrire ces lignes :

package com.example.aka.webservicemeteo.access; import android.os.Message; import android.util.Log; import com.example.aka.webservicemeteo.modele.WeatherObservation; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import org.json.JSONObject; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; import java.util.List; public class ThreadGetMeteo implements Runnable { private MonHandler monHandler; public ThreadGetMeteo(MonHandler monHandler) { this.monHandler = monHandler; } @Override public void run() { try { Log.i("suivi","Dans le ThreadGetMeteo, ouverture de l'url pour récupérer l'observation météo..."); URL url = new URL("http://api.geonames.org/findNearByWeatherJSON?lat=47.316667&lng=5.016667&username=levmc"); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); BufferedReader contenuWebRecu = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //Log.i("suivi","STREAM RECU" + contenuWebRecu.readLine()); String json = contenuWebRecu.readLine(); Gson gson = new Gson(); //on ne peut pas directement reconstruire un objet WeatherObservation car le webservice //renvoie un json avec WeatherObservation à la racine, c'est ballot //on va donc reconstruire dans une classe intermédiaire JsonObject jsonObject = gson.fromJson(json, JsonObject.class); //puis, depuis cette classe intermédiaire, on va récupérer tous les enfants de "weatherObservation" WeatherObservation weatherObservation = gson.fromJson(((JsonObject)jsonObject.get("weatherObservation")), WeatherObservation.class); Log.i("suivi","Reconstruction de l'observation météo à partir du JSON reçu..."+weatherObservation); contenuWebRecu.close(); //finalisation on ferme le flux Log.i("suivi","Exemple de weatherobservation :"+weatherObservation); Log.i("suivi","Ville :"+weatherObservation.getStationName()); Log.i("suivi","Température :"+weatherObservation.getTemperature()); Message message=new Message(); //on met l'objet weatherObservation dans le message à destination du handler message.obj=weatherObservation; //on envoie le message au handler monHandler.sendMessage(message); } catch (Exception e) { //il faudrait plutôt catcher différentes exceptions ici //"pas de réseau ?" "pas de réponse ?" "mauvaise réponse ?" Message message=new Message(); message.obj=e.getLocalizedMessage().toString(); //on envoie le message au handler monHandler.sendMessage(message); Log.i("suivi","Exception :"+e.getMessage()); }finally { Log.i("suivi","Terminé !"); } } }


Avec son handler :

package com.example.aka.testpersothreads; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class MonHandler extends Handler { private ActivitePrincipale activitePrincipale; public MonHandler(ActivitePrincipale ma) { activitePrincipale=ma; System.out.println("Creation du handler"); } @Override public void handleMessage(Message msg) { System.out.println("Un message a été reçu : "+msg.toString()); activitePrincipale.modifInterface(); } }


Et le main :

package com.example.aka.webservicemeteo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import com.example.aka.webservicemeteo.access.MonHandler; import com.example.aka.webservicemeteo.access.ThreadGetMeteo; public class ActivitePrincipale extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_activite_principale); MonHandler monHandler=new MonHandler(this); Thread t=new Thread(new ThreadGetMeteo(monHandler)); t.start(); } public void changerTexteCentralPar(String nouveauTexte) { TextView texteCentral= (TextView) findViewById(R.id.texteCentral); texteCentral.setText(nouveauTexte); } }