(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);
}
}