• 30 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 1/13/20

Développez une première application géolocalisée pour montre

Log in or subscribe for free to enjoy all this course has to offer!

Développement d'applications sur une montre

Une application Android Wear est architecturée comme une application Android standard. Lorsque deux applications (une pour le téléphone mobile, une pour la montre) sont créées dans un même projet Android Studio, on manipule alors deux arborescences dans un même projet Android Studio. Chaque application a son propre fichier de configuration gradle pour la compilation. Au déploiement, il faut choisir quelle application (wear ou mobile) on souhaite déployer sur la montre ou le téléphone.

Comme pour une application Android, une application Wear est constituée d’activités. Celles-ci sont des  WearableActivity  qui héritent de  Activity. Elles se comportent donc de la même façon, notamment du point de vue de leur cycle de vie. Le code d’une telle activité ressemble donc à :

public class MainActivity extends WearableActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setAmbientEnabled();
}
}

Elles ont cependant un comportement supplémentaire : la gestion des modes ambient et interactif que l’on peut utiliser pour changer le visuel d'une application suivant que l’on interagisse, ou pas, avec elle. Dans l’exemple ci-dessous, issu de la documentation de Google (CC-BY), on voit deux applications dans les deux modes. Par exemple, l’application pour faire les courses est colorée en vert et on voit clairement les cases à cocher. En mode ambient, l’application est en noir et blanc et les cases à cocher sont suggérées.

Deux exemples d'applications en modes ambients vs interactifs
Deux exemples d'applications en modes ambients et interactifs

Dans le mode interactif, tous les éléments graphiques peuvent être utilisés : boutons, images, et autres éléments interactifs. D’un point de vue des couleurs, tout est autorisé. Dans le mode ambient, l’application se doit de basculer en niveau de gris, et supprimer les éléments interactifs. En effet, dans ce mode, l’affichage reste longtemps allumé, à des fins de consultation par l’utilisateur, mais sans interaction. Ces règles sont précisées dans la documentation de Google.

Du point de vue du code, il faut surcharger les méthodes  onEnterAmbient(),  onUpdateAmbient()  et  onExitAmbient(), comme par exemple :

@Override
public void onEnterAmbient(Bundle ambientDetails) {
super.onEnterAmbient(ambientDetails);
updateDisplay();
}
@Override
public void onUpdateAmbient() {
super.onUpdateAmbient();
updateDisplay();
}
@Override
public void onExitAmbient() {
updateDisplay();
super.onExitAmbient();
}

 Ces méthodes appellent la méthode  updateDisplay()  qui va mettre à jour l’interface graphique :

private void updateDisplay() {
if (isAmbient()) {
mContainerView.setBackgroundColor(getResources().getColor(android.R.color.holo_red_dark));
mTextView.setTextColor(getResources().getColor(android.R.color.white));
mClockView.setVisibility(View.VISIBLE);
mClockView.setText(AMBIENT_DATE_FORMAT.format(new Date()));
} else {
mContainerView.setBackground(null);
mTextView.setTextColor(getResources().getColor(android.R.color.black));
mClockView.setVisibility(View.GONE);
}
}

Dans cet exemple, on fait disparaître ou apparaître les éléments graphiques et on change leurs couleurs. On évitera autant que faire se peut de générer des nouveaux éléments graphiques, car cela consomme des ressources supplémentaires. On obtient alors le comportement suivant :

Résultat du code précédent: ambient vs interactif
Résultat du code précédent: ambient vs interactif

Particularités des applications Wear

Il y a bien sûr certaines particularités propres aux montres connectées, en plus du mode ambient et interactif. Nous allons en voir deux dans cette section : la gestion des écrans ronds et la gestion de la géolocalisation.

Les gabarits et les écrans ronds

Lorsque l’on utilise les gabarits classiques tels que les  LinearLayout , il peut arriver que certains éléments graphiques soient mal représentés à l’écran car celui-ci n’est pas d’une forme classique, comme par exemple :

Utilisation d'un LinearLayout dans un monde rond
Utilisation d'un  LinearLayout  dans un monde rond

On peut même imaginer des montres triangulaires, hexagonales, etc., où le problème serait sans doute pire. Il faut donc adapter le gabarit. Une solution est de détecter la forme de l’écran et de choisir un gabarit adapté. Une solution plus facile à mettre en oeuvre consiste à utiliser l’élément racine  BoxInsetLayout  dans le gabarit, qui va contraindre les éléments graphiques pour les inscrire dans la forme de la montre.

Pour ce faire, il suffit de créer un gabarit comme celui-ci :

<?xml version="1.0" encoding="utf-8"?>
<android.support.wear.widget.BoxInsetLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
...
>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp"
app:boxedEdges="all">
</FrameLayout>
</android.support.wear.widget.BoxInsetLayout>

Attention à bien modifier l’attribut  app:boxedEdges  à "all" de l’élément enfant du  BoxInsetLayout, afin que le  BoxInsetLayout  calcule les placements à partir de cet élément.  De plus, pour les  BoxInsetLayout  de la nouvelle version de Wear (Android Wear 2),  il faut donc utiliser le tag  android.support.wear.widget.BoxInsetLayout  (et pas le tag   android.support.wearable.view.BoxInsetLayout ). Il faut en plus ajouter la dépendance wear dans le fichier de build gradle :

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:wear:26.1.0'
}

On obtient alors :

Utilisation d'un BoxInsetLayout dans un monde rond
Utilisation d'un  BoxInsetLayout  dans un monde rond

La géolocalisation

La gestion de la géolocalisation est aussi un peu particulière. La première question à se poser est de savoir si votre montre possède ou non un capteur GPS. Peu de montres en possèdent : seules les montres sportives qui enregistrent le tracé d’un running en sont pourvues en général. Pour les autres montres, ce n’est sans doute pas vital car la montre peut s’appuyer sur le téléphone mobile pour connaître sa géolocalisation. Ainsi, le code pourrait être difficile à écrire car il faudrait prendre en compte les deux situations suivantes, à gauche une montre Asus Zenwatch 3, dépourvue d’un capteur GPS, au centre, une montre Motorola 360 sport qui elle en possède un:

Géolocalisation avec ou sans capteur GPS
Géolocalisation avec ou sans capteur GPS

En fait, Google a résolu ce problème en proposant d’interroger la géolocalisation au travers des Google Play services d’Android Wear. Ce service fera le choix du mode de lecture appropriée de la géolocalisation (directement au travers du capteur ou via le téléphone). On se retrouve donc avec un code unifié faisant appel à ces services :

Géolocalisation via les Google Play services
Géolocalisation via les Google Play services

D’un point de vu du code, il reste tout de même pas mal de travail à effectuer.

Il faut tout d’abord déclarer les permission nécessaires dans le Manifest :

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

On peut aussi préciser que notre application utilise le composant hardware GPS, mais qu’il n’est pas obligatoire (sinon notre application ne pourrait pas s’installer sur une montre dépourvue du capteur) :

<uses-feature android:name="android.hardware.location.gps" android:required="false" />

On peut ensuite tester explicitement la présence de ce capteur :

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS))

Cependant, cela ne changera rien au code qui suit et qui utilise les Google services. Pour ce faire, il faut se connecter au service :

GoogleApiClient gapi = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
gapi.connect();

 En précisant bien que l’on souhaite utiliser  LocationServices.API.

Comme la callback de connection est “this”, votre  WearActivity  doit implémenter  GoogleApiClient.ConnectionCallbacks  et doit, entre autre, avoir la méthode  onConnected(Bundle bundle) qui sera appelée lorsque votre application sera connectée au service. A partir de ce moment, on pourra interroger la géolocalisation.

La géolocalisation utilise l’objet  FusedLocationProviderClient flpc , que l’on récupère via un appel static :

// Get the location provider through the GoogleApi
FusedLocationProviderClient flpc = LocationServices.getFusedLocationProviderClient(this);

Ensuite, pour obtenir une géolocalisation, si elle n’a jamais encore été obtenue, on va demander à être notifié régulièrement grâce à un  LocationRequest . Celui-ci est paramétré pour être notifié toutes les secondes si la géolocalisation change. On peut ajouter une callback pour exécuter du code dans ce cas, mais ce n’est pas nécessaire dans notre cas.

// Ask for update of the location
LocationRequest locationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(1000).setFastestInterval(1000);
lc = new LocationCallback();
flpc.requestLocationUpdates(locationRequest, lc, null);

Enfin, il faut lire la géolocalisation (par exemple dans le code d’un bouton). Pour ce faire, toujours sur l’objet  FusedLocationProviderClient flpc , on appelle la méthode  getLastLocation(). Celle-ci s’exécute de manière asynchrone et renvoie donc un  Task<Location>.

Task<Location> loc = flpc.getLastLocation();
loc.addOnSuccessListener(this);

Pour exécuter du code en fin de tâche asynchrone, il faut ajouter un écouteur, à l’aide de la méthode  addOnSuccessListener. Dans ce code, on met this en paramètre, ce qui oblige la  WearActivity  à implémenter l’interface  OnSuccessListener<Location> , c’est-à-dire la méthode  onSuccess(Location loc)  qui va gérer le code qui s’exécute quand la tâche asynchrone termine avec succès. Le code en question va lire la géolocalisation et mettre à jour l’interface graphique :

// Callback for the location task
@Override
public void onSuccess(Location loc) {
Log.i("CIO", "Task completed.");
if (loc != null) {
DecimalFormat df = new DecimalFormat("#.##");
String lon = df.format(loc.getLongitude());
String lat = df.format(loc.getLatitude());
Log.i("CIO", "Location: " + lon + " , " + lat );
TextView latTV = (TextView)findViewById(R.id.lat);
latTV.setText("latitude: " + lon);
TextView lonTV = (TextView)findViewById(R.id.lon);
lonTV.setText("longitude: " + lat);
}

Au final, on obtient bien la lecture de la géolocalisation sur les deux montres, et que le capteur soit présent ou non :

Géolocalisation sur la ZenWatch3 et la Moto 360 Sport
Géolocalisation sur la ZenWatch3 et la Moto 360 Sport
Example of certificate of achievement
Example of certificate of achievement