Accueil > Non classé > Android pour l’entreprise – 1 – Premiers contacts

Android pour l’entreprise – 1 – Premiers contacts

Cet article est le premier d’une série consacrée au thème : Android pour l’entreprise.

Android est à la fois un système d’exploitation pour téléphones mobiles, ainsi qu’une plateforme de développement d’applications se déployant sur ce système d’exploitation.

Logo Android

Nous allons étudier, d’un point de vue technique, les fonctionnalités à notre disposition pour intégrer pleinement la mobilité au sein du système d’information.

Cette série d’articles  :

  • décrit des fonctionnalités d’Android utiles au développement d’applications pour l’entreprise,
  • vous redirige en temps utile vers des ressources externes pour tous les concepts de base, afin que vous ne soyez pas perdu et que vous n’ayez pas à demander à l’ami Google.

Cette série d’articles n’est pas :

  • dédiée aux fonctionnalités permettant de réaliser des jeux 3D / applications média / apps vues à la télé,
  • un tutoriel / cours pour apprendre tous les détails de la création d’applications Android.

Logo YMCALe fil rouge de cet article sera la réalisation d’un annuaire de contacts se branchant sur un SI : Your Mobile Contact Application (YMCA).

YMCA : besoins fonctionnels

La demande initiale est simple, et évoluera par la suite de manière itérative :

  • en lançant l’application, un utilisateur voit la liste de contacts, issue du système d’information de l’entreprise,
  • chaque élément de la liste comprend le nom du contact, et son numéro de téléphone,
  • lorsque l’utilisateur tapote un élément de la liste, l’application compose le numéro de téléphone et l’utilisateur peut appeler.
YMCA : besoins fonctionnels en deux écrans

YMCA : besoins fonctionnels en deux écrans

Les sources du projet sont disponibles à la fin de cet article.

Android en quelques mots

Les applications Android sont développées en Java, et exécutées au sein d’une JVM spécifique : DalvikVM.

DukePhoning_smallLe développeur a accès à une grande partie de l’API J2SE (on notera par exemple l’absence de javax.swing, java.awt, etc). De nombreuses bibliothèques Java peuvent ainsi être intégrées aux applications (exemple : Joda Time).

Une application est livrée sous la forme d’un fichier .apk, qui contient notamment les .class recompilés pour DalvikVM.

Pour un aperçu global de ce qu’est Android, cliquez-ici.

Une application comporte quatre types de composants :

  • Activity : correspond à un écran de l’application, à l’instar d’une page Web au sein d’une application Web. Au sein d’une application, l’utilisateur navigue d’activité en activité, et dispose d’un bouton back permettant à tout moment de revenir à l’activité précédente.
  • Service : composant qui s’exécute en tâche de fond, par exemple pour consulter les mails de l’utilisateur à intervalles réguliers.
  • Broadcast Receiver : composant qui s’exécute en présence d’événements particuliers, tel que l’arrivée d’un SMS.
  • Content Provider : permet à une application de fournir du contenu spécifique à d’autres applications. Notre annuaire de contact pourrait ainsi fournir sa liste de contacts à une application d’envoi de SMS.

Pour plus de détails sur les fondamentaux d’Android, cliquez-ici. N’hésitez pas, la documentation est complète et très bien rédigée.

Let’s start!

La première étape est naturellement d’installer le SDK Android, ainsi que le plugin Eclipse (ADT). Téléchargez le SDK, puis suivez donc la procédure officielle d’installation.

Nous allons ensuite créer le projet YMCA, et l’exécuter sur l’émulateur. Une fois de plus, tout est dans la doc : création et exécution d’un nouveau projet.

Voici les informations utilisées pour créer le projet :

  • Project name : YMCA
  • Build  Target : Android 1.5 (le projet sera compatible Android 1.5 et 1.6, et nous n’allons pas utiliser l’API Google pour le moment)
  • Application name : YMCA
  • Package name : com.excilys.ymca
  • Create activity : ContactListActivity
  • Min SDK Version : 3 (correspond à Android 1.5)

Une fois le projet créé et exécuté, vous devriez voir ceci au sein de l’émulateur :

hello_contact_activity

YMCA, projet nouvellement créé

Vous remarquerez que le skin de votre émulateur est loin d’être aussi sexy. Il est possible d’en changer, en suivant ce lien.

Par ailleurs, n’hésitez pas à afficher les logs, en entrant dans une console la commande : adb logcat . Cela se révèlera bien utile en cas d’exception surprise.

Logs de l'émulateur

Logs de l'émulateur

AndroidManifest.xml

Afin de structurer le projet, déplacez la classe com.excilys.ymca.ContactListActivity dans un nouveau package : com.excilys.ymca.activity, qui contiendra les activités.

L’éditeur vous indique alors à l’aide d’une petite croix rouge que le fichier AndroidManifest.xml n’est plus valide.

Ce fichier décrit les caractéristiques de l’application, et notamment les activités disponibles. Modifiez le nom de l’activité de .ContactListActivity en .activity.ContactListActivity . Le nom complet de la classe doit correspondre au nom du package de la balise manifest + le nom de l’activité de la balise activity.

Étant donné que notre application aura besoin d’accéder à Internet pour obtenir la liste des contacts, profitons-en pour ajouter la balise suivante, du même niveau que la balise application :

1
<uses-permission android:name="android.permission.INTERNET" />

Bravo !!

Vous venez de découvrir l’une des grandes forces d’Android : la sécurité. La plupart des fonctionnalités permettant aux applications Android de sortir de leur vase clos nécessitent des permissions, qui doivent être déclarées dans le manifest. Lorsqu’un utilisateur souhaite installer une application, la liste des permissions requises par l’application lui est notifiée. L’utilisateur connaît précisement le degré de liberté de l’application.

Il en est ainsi pour l’accès à Internet, l’accès au GPS, l’accès en lecture à la liste des contacts, l’accès en écriture à la liste des contacts. Il est même possible de créer des permissions afin que seules les applications autorisées puissent accéder aux services exposés par votre application.

C’est une garantie très importante pour les applications d’entreprise : aucune application externe ne pourra accéder aux données de l’application d’entreprise sans que l’utilisateur n’ait explicitement donné son accord.

Le fichier AndroidManifest.xml devrait désormais ressembler à ceci :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.excilys.ymca"
     android:versionCode="1"
     android:versionName="1.0">

    <uses-permission android:name="android.permission.INTERNET" />  

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".activity.ContactListActivity"
                 android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <uses-sdk android:minSdkVersion="3" />

</manifest>

Le métier

Je vous propose de commencer par écrire les classes métier. Comme nous allons le voir, cela se passe exactement comme dans toute application Java :

  1. Créer une entité, Contact, qui possède un nom et un numéro de téléphone.
  2. Créer une interface pour définir le contrat du service permettant d’obtenir la liste des contacts
  3. Créer diverses implémentations, liées à la manière d’obtenir les contacts.
  4. Créer une factory permettant d’obtenir l’implémentation choisie.

Ce qui donne, en UML :

YMCA, composants métier

YMCA, composants métier

Créer l’entité Contact :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.excilys.ymca.model;

public class Contact {

    private final String    name;
    private final String    phoneNumber;

    public Contact(String name, String phoneNumber) {
        this.name = name;
        this.phoneNumber = phoneNumber;
    }

    public String getName() {
        return name;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

}

Créer l’interface IContactListService :


1
2
3
4
5
6
7
8
package com.excilys.ymca.service;

import java.util.List;
import com.excilys.ymca.model.Contact;

public interface IContactListService {
    List<Contact> getContactList();
}

Créer une implémentation mock de l’interface IContactListService, ContactListServiceMockImpl :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.excilys.ymca.service.mock;

import java.util.ArrayList;
import java.util.List;

import com.excilys.ymca.model.Contact;
import com.excilys.ymca.service.IContactListService;

public class ContactListServiceMockImpl implements IContactListService {
    @Override
    public List<Contact> getContactList() {
        ArrayList<Contact> contacts = new ArrayList<Contact>();

        contacts.add(new Contact("Agathe Zepoweur", "06 01 01 01 01"));
        contacts.add(new Contact("François Pignon", "06 02 02 02 02"));
        contacts.add(new Contact("Gérard Manvussa", "06 03 03 03 03"));
        contacts.add(new Contact("Hubert Bonisseur de la Batte", "06 04 04 04 04"));
        contacts.add(new Contact("Odile Deray", "06 05 05 05 05"));
        contacts.add(new Contact("Olivier Louarne", "06 06 06 06 06"));
        contacts.add(new Contact("Salah Kis", "06 07 07 07 07"));

        return contacts;
    }
}

Cette implémentation permettra de tester rapidement le fonctionnement de l’application.

N’oublions pas que le but est d’utiliser un annuaire distant, accessible via Internet.

Je vous propose donc de mettre en place sur un serveur (en l’occurrence, le localhost) un fichier contacts.txt, qui fera office de service d’annuaire du système d’information. Voici son contenu :

Agathe Zepoweur|06 01 01 01 01
François Pignon|06 02 02 02 02
Gérard Manvussa|06 03 03 03 03
Hubert Bonisseur de la Batte|06 04 04 04 04
Odile Deray|06 05 05 05 05
Olivier Louarne|06 06 06 06 06
Salah Kis|06 07 07 07 07

Dès lors, il ne reste plus qu’à créer l’implémentation ContactListServiceRemoteFileImpl :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.excilys.ymca.service.remotefile;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import android.util.Log;

import com.excilys.ymca.model.Contact;
import com.excilys.ymca.service.IContactListService;

public class ContactListServiceRemoteFileImpl implements IContactListService {

    private static final String TAG = ContactListServiceRemoteFileImpl.class.getSimpleName();
    private String              fileUrl;

    @Override
    public List<Contact> getContactList() {

        ArrayList<Contact> contacts = new ArrayList<Contact>();

        try {
            URL url = new URL(fileUrl);

            InputStream is = url.openStream();
            try {
                BufferedReader br = new BufferedReader(new InputStreamReader(is));

                String line;
                while ((line = br.readLine()) != null) {
                    StringTokenizer stk = new StringTokenizer(line, "|");
                    contacts.add(new Contact(stk.nextToken(), stk.nextToken()));
                }
            } finally {
                if (is != null) {
                    is.close();
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "Huhu, something bad happened", e);
        }
        return contacts;
    }

    public void setFileUrl(String fileUrl) {
        this.fileUrl = fileUrl;
    }
}

Remarquez l’utilisation de android.util.Log qui permet de logguer des informations. La méthode e() logue un message de niveau Error. Le premier paramètre est la source du message, le second le message d’erreur, et le troisième une exception, afin de loguer la stacktrace le cas échéant.

Créer la fabrique statique des services, StaticContactListFactory :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.excilys.ymca.service;

import com.excilys.ymca.service.mock.ContactListServiceMockImpl;
import com.excilys.ymca.service.remotefile.ContactListServiceRemoteFileImpl;

public class StaticContactListFactory {

    private StaticContactListFactory() {
    }

    public static IContactListService getMockService() {
        return new ContactListServiceMockImpl();
    }

    public static IContactListService getRemoteFileService() {
        ContactListServiceRemoteFileImpl service = new ContactListServiceRemoteFileImpl();
        service.setFileUrl("http://10.0.2.2/contacts.txt");
        return service;
    }

}

Mais quelle est cette ip mystérieuse : 10.0.2.2 ?

Il s’agit de l’ip correspondant au localhost du host de l’émulateur (c’est à dire votre système d’exploitation). En effet, “127.0.0.1” ou “localhost” est résolu comme le localhost de l’émulateur. Pour faire simple : au lieu de “localhost” ou “127.0.0.1” pour accéder à votre serveur local, il faut utiliser 10.0.2.2 .

Activity et layout

Examinons maintenant la classe ContactListActivity :


1
2
3
4
5
6
7
8
public class ContactListActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

La méthode onCreate() est appelée par le système à la création de l’activité. La méthode setContentView() permet de définir le layout utilisé, qui définit les éléments composant l’écran.

Rentrer plus précisement dans les détails sort de la portée de cet article. Cependant, pour une bonne compréhension de ce qui va suivre, je vous conseille fortement de lire les pages suivantes de la documentation :

Les éléments du dossier /res (les fichiers XML ainsi que les éléments qu’ils contiennent) sont accessibles depuis le code Java via des identifiants de ressource. Ces identifiants sont des entiers, dont la valeur est définie au sein de la classe R, qui est auto-générée.

Afin de gagner en clarté, changez le nom du fichier /res/layout/main.xml en contact_list.xml . Attention, dès lors, la classe ContactListActivity ne compile plus. En effet, l’identifiant de layout n’est plus R.layout.main mais R.layout.contact_list.

Profitez-en pour ajouter deux composants graphiques au layout : un TextView (permettant d’afficher un text) et une ListView (pour afficher la liste de contacts).

Voici le contenu de contact_list.xml :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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:text="@string/contact_list_tip"
               android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:textStyle="bold"
               android:textSize="20dip"
               android:textColor="#ef3e42"/>

    <ListView   android:id="@+id/contact_list"
               android:layout_width="fill_parent"
               android:layout_height="fill_parent" />

</LinearLayout>

La notation @+id dans la ListView permet de spécifier qu’un identifiant doit être créé dans la classe R, ce qui permettra d’accéder au composant et de le manipuler.

De plus, notez la présence d’attributs de mise en forme sur le composant TextView.

Dernier point, mais non des moindres : la valeur du texte dans la TextView ne se trouve pas directement dans le XML du layout. La notation “@string/contact_list_tip” marque une référence vers une chaîne de caractère située dans un autre fichier de ressource : res/values/strings.xml . Voici son contenu :


1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">YMCA</string>
    <string name="contact_list_activity_title">YMCA : Your Mobile Contact App</string>
    <string name="contact_list_tip">Tapotez un nom pour appeler</string>
</resources>

C’est une bonne habitude à prendre : Android permet d’externaliser toutes les chaînes de caractères de l’application dans un fichier de ressource, et de créer une version du fichier pour chaque langue.

La sélection du bon fichier de ressource est automatique, en fonction de la langue du téléphone. A ce sujet, lire la page : Resources and Internationalization .

Ces fonctionnalités sont des atouts majeurs lors de la réalisation d’applications pour l’entreprise.

L’application se présente désormais ainsi :

ListView vide

ListView vide

La ListView n’apparaît pas, étant donné qu’elle ne contient aucun élément.

Assemblage des legos

Il ne reste plus qu’à utiliser nos composants métier pour obtenir des données et les lier à nos composants graphiques, et enfin ajouter un comportement dynamique à ces composants graphiques.

Cet assemblage est réalisé dans la méthode onCreate(), après l’appel à setContentView(R.layout.contact_list);.

Récupération de la liste de contacts (placée en variable d’instance de l’activité) :


1
2
3
//IContactListService contactListService = StaticContactListFactory.getMockService();
IContactListService contactListService = StaticContactListFactory.getRemoteFileService();
contacts = contactListService.getContactList();

Récupération du composant graphique de liste :


1
ListView contactListView = (ListView) findViewById(R.id.contact_list);

Insertion des données dans les composants graphiques :

1
2
3
setTitle(R.string.contact_list_activity_title);
ContactAdapter adapter = new ContactAdapter(this, contacts);
contactListView.setAdapter(adapter);

ContactAdapter est une classe permettant d’adapter les données au composant graphique :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.excilys.ymca.view;

import java.util.List;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import com.excilys.ymca.model.Contact;

public class ContactAdapter extends ArrayAdapter<Contact> {

    public ContactAdapter(Context context, List<Contact> contacts) {
        super(context, android.R.layout.two_line_list_item, contacts);
    }

    @Override
    public View getView(int position, View listItem, ViewGroup parent) {

        if (listItem == null) {
            listItem = View.inflate(getContext(), android.R.layout.two_line_list_item, null);
        }
        Contact contact = getItem(position);

        TextView text1 = (TextView) listItem.findViewById(android.R.id.text1);
        TextView text2 = (TextView) listItem.findViewById(android.R.id.text2);

        text1.setText(contact.getName());
        text2.setText(contact.getPhoneNumber());

        return listItem;
    }

}

L’identifiant android.R.layout.two_line_list_item est un composant à deux lignes, qui est ici utilisé comme layout pour chaque élément de la liste.

Notez qu’on redéfinit la méthode getView, chargée de renvoyer le composant graphique d’un élément à une position donnée dans la liste. Cela permet de customiser ce composant graphique.

Binding to Data with AdapterView vous permettra d’en apprendre plus sur le sujet.

Ajout d’un comportement à la ListView :


1
2
3
4
5
6
7
contactListView.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        String phoneNumber = contacts.get(position).getPhoneNumber();
        dialPhoneNumber(phoneNumber);
    }
});

On enregistre ici un listener à la ListView, dont la méthode onItemClick est appellée chaque fois qu’un élément de la liste est cliqué.

Enfin, la méthode dialPhoneNumber :


1
2
3
4
private void dialPhoneNumber(String phoneNumber) {
    Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber));
    startActivity(intent);
}

La méthode startActivity permet de démarrer une nouvelle activité. Il peut s’agir d’une activité au sein de l’application, ou d’une activité extérieure à l’application.

La classe Intent permet de définir une intention, dans le cas présent il s’agit d’exécuter l’action de composition d’un numéro, ce numéro étant fourni en paramètre avec un format d’uri particulier.

Notre application n’a pas à savoir quelle application prendra le relai pour composer le numéro. Cela permet un couplage très faible entre les différentes applications, sans empêcher une collaboration étroite inter applications. En somme, c’est le principe d’une architecture orientée services.

Pour en savoir plus : Intents and Intent Filters .

Code complet de ContactListActivity :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.excilys.ymca.activity;

import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;

import com.excilys.ymca.R;
import com.excilys.ymca.model.Contact;
import com.excilys.ymca.service.IContactListService;
import com.excilys.ymca.service.StaticContactListFactory;
import com.excilys.ymca.view.ContactAdapter;

public class ContactListActivity extends Activity {

    private ListView        contactListView;
    private List<Contact>   contacts;

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

        setContentView(R.layout.contact_list);

        retrieveData();
        findViews();
        addContent();
        addListeners();
    }

    private void addContent() {
        setTitle(R.string.contact_list_activity_title);
        ContactAdapter adapter = new ContactAdapter(this, contacts);

        contactListView.setAdapter(adapter);
    }

    private void addListeners() {
        contactListView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                String phoneNumber = contacts.get(position).getPhoneNumber();
                dialPhoneNumber(phoneNumber);
            }
        });
    }

    private void retrieveData() {
        // IContactListService contactListService =
        // StaticContactListFactory.getMockService();

        IContactListService contactListService = StaticContactListFactory.getRemoteFileService();

        contacts = contactListService.getContactList();
    }

    private void findViews() {
        contactListView = (ListView) findViewById(R.id.contact_list);
    }

    private void dialPhoneNumber(String phoneNumber) {
        Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber));
        startActivity(intent);
    }
}

Final touch

Pensez à changer l’icône de lancement de l’application : /res/drawable/icon.png .

Pour déployer l’application sur un vrai téléphone, il faut au préalable signer le .apk généré .

Ensuite, il suffit de la placer sur la carte SD du téléphone et de l’installer à l’aide de l’application “Installer” disponible sur l’Android Market.

It’s fun to play with YMCA

Comme promis :

Dans cet article, nous avons étudié plusieurs aspects d’Android intéressants pour l’univers des applications d’entreprise :

  • l’intégration entre plusieurs applications et avec les fonctionnalités fournies par le système d’exploitation (ici, composer un numéro),
  • la sécurité, via la gestion des permissions,
  • l’internationalisation (i18n) des applications, et une localisation (l10n) rapide.

Si vous souhaitez poursuivre l’exercice, pourquoi ne pas créer d’autres implémentations de IContactListService, branchées sur de vrai annuaires cette fois ci ? A vous de jouer !

Et ensuite ?

L’épisode suivant marque une intégration plus poussée de l’application au sein du monde Android.

Google MapsAu programme : ajout de contacts directement dans l’annuaire du téléphone, et positionnement de l’adresse d’un contact sur Google Maps.

Il est disponible à l’adresse suivante : Android pour l’entreprise – 2 – Vous habitez chez vos parents ?

N’hésitez pas à laisser des commentaires, sur le fond comme sur la forme, toute remarque est bienvenue.

Share
  1. Alexis THOMAS
    21/01/2010 à 17:11 | #1

    * sous Windows, le plugin ADT peut poser problème si le nom d’utilisateur sur la machine contient un caractère spécial ou accentué : Issue 4027. La solution est décrite rapidement dans le ticket (déplacer l’avd et modifier le .ini).

    * on peut repréciser rapidement que pour “mettre en place sur un serveur (en l’occurrence, le localhost) un fichier contacts.txt”, il s’agit d’installer ou d’avoir un serveur http ou web ou autre, et de placer le fichier pour qu’il se trouve à la racine du localhost. Apache est par exemple une solution simple et rapide (soit brut, soit par Wamp entre autres).

    * l’assemblage des legos se passe très bien dans le onCreate, mais on notera que le “Code complet de ContactListActivity” proposé a été refactoré pour avoir un découpage en méthodes, et donc améliorer la lisibilité surement.

  1. 08/02/2010 à 12:33 | #1
  2. 19/05/2010 à 11:05 | #2