Créer une application Wear avec Emmet et DaVinci


Si vous avez déjà essayé de créer une application Android Wear, vous savez à quel point le développement de ce deuxième module est long. Vous devez utiliser la Google Data API pour synchroniser chaque envoie, ce qui deviens assez compliqué lorsque l’on a besoin d’afficher une image sur la montre, ou envoyer une liste de données. Cette étape prend le plus souvent plusieurs heures à mettre en place, alors que nous devrions nous concentrer sur le vrai développement de l’application wear, les interactions, le design, etc.

Pour simplifier le développement du module Wear et aussi accélérer sa création, j’ai créé 2 librairies :

  • DaVinci, permettant le téléchargement et la mise en cache d’une image à la façon de Picasso
  • Emmet, simplifiant l’échange de données entre Smartphone et Smartwatch, à la façon de Retrofit

https://github.com/florent37/DaVinci

https://github.com/florent37/Emmet

Regardons ensemble comment utiliser ces librairies.

DaVinci

DaVinci est disponible sur JCenter, c’est à dire qu’il est possible de l’importer depuis gradle en ajoutant dans votre fichier build.gradle

compile ('com.github.florent37:davinci:1.0.2@aar'){
    transitive=true
}

DaVinci est composée de 2 modules :

  • DaVinci : à importer dans votre module Wear
  • DaVinciDaemon :à importer dans votre module Smartphone (ou Tablette)

wear/build.gradle

compile ('com.github.florent37:davinci:1.0.2@aar'){
    transitive=true
}

mobile/build.gradle

compile ('com.github.florent37:davincidaemon:1.0.2@aar'){
    transitive=true
}

Utiliser DaVinci

Si vous êtes un adepte de Picasso, DaVinci sera facile à prendre en main, la syntaxe est exactement la même :

Pour charger une image depuis un PATH

DaVinci.with(context).load("/image/0").into(imageView);

Ou directement depuis une URL !

DaVinci.with(context).load("http://i.imgur.com/o3ELrbX.jpg").into(imageView);

N’oubliez pas d’activer l’écriture sur le disque pour bénéficier du cache

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

Pour simplifier son utilisation j’ai automatisé son utilisation depuis un GridViewPager, ce sera utile pour notre exemple

@Override
public Drawable getBackgroundForRow(final int row) {
    return DaVinci.with(context).load("/image/" + row).into(this, row);
}

Utiliser DaVinciDaemon

DaVinci a besoin de son DaVinciDaemon coté smartphone, qui a pour role de répondre aux requêtes du module wear et envoyer les bitmap demandées.

DaVinciDaemon a juste besoin d’écouter les messages reçus par le service wear sur smartphone :
Dans votre WearableListenerService

@Override
public void onMessageReceived(MessageEvent messageEvent) {
    super.onMessageReceived(messageEvent);
    DaVinciDaemon.with(getApplicationContext()).handleMessage(messageEvent);
    ...
}

Il est possible de pré-charger des images afin qu’elles soient directement disponibles sur la montre
(Cette étape n’est pas indispensable, mais elle accélère le temps de chargement des images)
Depuis le module mobile/

DaVinciDaemon.with(getApplicationContext()).load("http://i.imgur.com/o3ELrbX.jpg").send();

Emmet

Emmet permet un échange de données selon un protocole entre Wear et Smartphone. Cela veux dire que l’on va définir une façon de communiquer qui sera utilisée par nos 2 modules, dans notre cas il suffit de définit une interface sur lequel nous pourrons appeler des fonctions. Chaque appel de fonction depuis le module wear/ sera transmit au module  mobile/ et inversement.

Envoyer un message de notre Wear vers le Smartphone

Dans l’exemple suivant, je vais envoyer un message sayHello depuis ma Smartwatch vers mon Smartphone :

1. Je crée un module librairie : wearprotocol

2. J’importe Emmet dans ce module

wearprotocol/build.gradle

apply plugin: 'com.android.library'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'

    compile ('com.github.florent37:emmet:1.0.4@aar'){
        transitive=true
    }
}

3. Je définit mon protocole / interface

public interface SmartphoneProtocol{
     public void sayHello();
}

4. Je relie mon activity à Emmet puis créé un Sender depuis mon module wear/

Ne pas oublier d’ajouter la dépendance au module wearprotocol

wear/build.gralde

dependencies{
    compile project(':wearprotocol')
}

wear/ MainActivity.java

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Emmet.onCreate(this);

        SmartphoneProtocol smartphoneProtocol = Emmet.createSender(SmartphoneProtocol.class);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Emmet.onDestroy();
    }
}

5. J’appelle sayHello() sur mon Sender

smartphoneProtocol.sayHello();

Recevoir les messages coté Smartphone

1. Relier notre service à Emmet : Pour cela, le plus simple est d’étendre EmmetWearableListenerService

Ne pas oublier d’ajouter la dépendance au module wearprotocol

mobile/build.gralde

dependencies{
    compile project(':wearprotocol')
}

mobile/ WearService.java

public class WearService extends EmmetWearableListenerService implements WearProtocol {

}

2. Enregistrer un Receiver : nous pouvons enregistrer directement le servie en Receiver en lui faisant implémenter SmartphoneProtocol

public class WearService extends EmmetWearableListenerService implements SmartphoneProtocol {

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

        Emmet.registerReceiver(SmartphoneProtocol.class,this);
    }

    @Override
    public void sayHello() { //        Log.d(TAG,"sayHello");
    }

}

Pas de crainte vous pouvez envoyer utiliser des fonctions contenant des paramètres, ils seront transmits.

Envoyer un message depuis notre Smartphone vers notre Wear

1. Cela se fait de la même manière, il suffit de définir un protocole, par exemple WearProtocole

public interface WearProtocol{
    public void sayReceived(String text);
}

2. Puis de créer un Sender coté Smartphone dans notre service et de lui appeler notre fonction sayReceived

public class WearService implements SmartphoneProtocol {

    private WearProtocol wearProtocol;

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

        Emmet.registerReceiver(SmartphoneProtocol.class,this);
        WearProtocol wearProtocol = Emmet.createSender(WearProtocol.class);
    }

    @Override
    public void sayHello() { //        Log.d(TAG,"sayHello");
        wearProtocol.sayReceived("Yes I received it");
    }

}

3. Créer un Receiver coté Wear

public class MainActivity extends Activity implements WearProtocol{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Emmet.onCreate(this);

        SmartphoneProtocol smartphoneProtocol = Emmet.createSender(SmartphoneProtocol.class);
        Emmet.registerReceiver(WearProtocol.class,this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Emmet.onDestroy();
    }

    @Override
    public void sayReceived(String text){  //        Log.d(TAG,"sayReceived "+text);
    }
}

Exemple

J’ai placé un fichier JSON contenant la liste des versions d’android, ainsi que leur logo respectif, essayons de les afficher sur notre montre grâce à Emmet et DaVinci !

Nous utiliserons Retrofit pour récupérer les données du JSON : http://pastebin.com/raw.php?i=PHPXBsEf

Créons notre protocole

Le protocol Smartphone->Wear
wearprotocole/ WearProtocole.java

public interface WearProtocol {
    void onAndroidVersionsReceived(List androidVersions);
}

Le protocol Wear->Smartphone
wearprotocole/ SmartphoneProtocol.java

public interface SmartphoneProtocol {
    void hello();
}

Puis nos objets models partagés

wearprotocole/ AndroidVersion.java

public class AndroidVersion {

    private String titre;
    private String description;
    private String url;

    //getters & setters
}

Coté Smartphone

mobile/build.gralde

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    wearApp project(':wear')
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile 'com.squareup.retrofit:retrofit:1.9.0'

    compile project (":wearprotocol")
    compile ("com.github.florent37:davincidaemon:1.0.2@aar"){
        transitive=true
        exclude group:"com.google.android.gms" //davinci & emmet use different gms versions
    }
}

Dans notre module smartphone, créer un interface pour Retrofit

AndroidService.java

public interface AndroidService {

    public static final String ENDPOINT = "http://pastebin.com";

    @GET("/raw.php?i=PHPXBsEf")
    public void getElements(Callback<List> callback);
}

Implémentons notre service Smartphone

mobile/ WearService.java

public class WearService extends EmmetWearableListenerService implements SmartphoneProtocol {

    WearProtocol wearProtocol;

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

        //initialise la récéption de données
        Emmet.registerReceiver(SmartphoneProtocol.class, this);

        //initialise l'envoie de données vers la montre
        wearProtocol = Emmet.createSender(WearProtocol.class);
    }

    //lorsque le smartphone reçois le message hello (envoyé depuis wear)
    @Override
    public void hello() {
        //Utilise Retrofit pour réaliser un appel REST
        AndroidService androidService = new RestAdapter.Builder()
                .setEndpoint(AndroidService.ENDPOINT)
                .build().create(AndroidService.class);

        //Récupère et deserialise le contenu de mon fichier JSON en objet List
        androidService.getElements(new Callback<List>() {
            @Override
            public void success(List androidVersions, Response response) {

                //envoie cette liste à la montre
                wearProtocol.onAndroidVersionsReceived(androidVersions);
            }

            @Override
            public void failure(RetrofitError error) {
            }
        });
    }

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        super.onMessageReceived(messageEvent);

        //permet à DaVinciDaemon d'écouter les messages
        DaVinciDaemon.with(getApplicationContext()).handleMessage(messageEvent);
    }
}

Ne pas oublier de le déclarer dans notre mobile/androidManifest.xml


Coté Wear

wear/build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.support:wearable:1.1.0'
    compile project(':wearprotocol')
    compile ('com.github.florent37:davinci:1.0.2@aar'){
        transitive=true
        exclude group:"com.google.android.gms" //davinci & emmet use different gms versions
    }
}

wear/ layout/activity_main.xml

<?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">
 
    <android.support.wearable.view.GridViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:keepScreenOn="true" />
 
    <android.support.wearable.view.DotsPageIndicator
        android:id="@+id/page_indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="5dp"/>
 
</FrameLayout>

Utilisons Emmet afin de communiquer avec notre smartphone

Envoie :

SmartphoneProtocol smartphoneProtocol = Emmet.createSender(SmartphoneProtocol.class);
smartphoneProtocol.hello(); //envoie le message hello smartphone

Reception :

Emmet.registerReceiver(WearProtocol.class, this);

wear/ MainActivity.java

public class MainActivity extends Activity implements WearProtocol {

    private final static String TAG = MainActivity.class.getCanonicalName();

    private GridViewPager pager;
    private DotsPageIndicator dotsPageIndicator;

    //la liste des éléments à afficher
    private List elementList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        pager = (GridViewPager) findViewById(R.id.pager);
        dotsPageIndicator = (DotsPageIndicator) findViewById(R.id.page_indicator);
        dotsPageIndicator.setPager(pager);

        //initialise Emmet
        Emmet.onCreate(this);

        Emmet.registerReceiver(WearProtocol.class, this);
        SmartphoneProtocol smartphoneProtocol = Emmet.createSender(SmartphoneProtocol.class);

        smartphoneProtocol.hello(); //envoie le message hello smartphone
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Emmet.onDestroy(); //ne pas oublier
    }

    //envoyé depuis le smartphone
    @Override
    public void onAndroidVersionsReceived(List androidVersions) {
        if (androidVersions != null && this.elementList != null && this.elementList.isEmpty()) {
            this.elementList.addAll(androidVersions);
            startMainScreen();
        }
    }

    public void startMainScreen() {
        //penser à effectuer les actions graphiques dans le UIThread
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //nous affichons ici dans notre viewpager

                if (pager != null && pager.getAdapter() == null)
                    pager.setAdapter(new ElementGridPagerAdapter(MainActivity.this, elementList, getFragmentManager()));
            }
        });
    }
}

Puis enfin créons notre adapter, qui affichera une cards pour chaque version d’android

et utilisons DaVinci pour afficher chaque logo en background de notre FragmentGridPagerAdapter

wear/ ElementGridPagerAdapter.java

public class ElementGridPagerAdapter extends FragmentGridPagerAdapter {

    private Context context;
    private List androidVersions;
    private List mRows;

    public ElementGridPagerAdapter(Context context, List androidVersions, FragmentManager fm) {
        super(fm);

        this.context = context;
        this.androidVersions = androidVersions;
        this.mRows = new ArrayList();

        //Construit le tableau des éléménts à afficher
        for (AndroidVersion version : androidVersions) {
            mRows.add(new Row(
                            //pour l'instant nous ne mettrons qu'un élément par ligne
                            CardFragment.create(version.getTitre(), version.getDescription())
                    )
            );
        }
    }

    //Le fragment à afficher
    @Override
    public Fragment getFragment(int row, int col) {
        Row adapterRow = mRows.get(row);
        return adapterRow.getColumn(col);
    }

    //le drawable affichée en background pour la ligne [row]
    @Override
    public Drawable getBackgroundForRow(final int row) {
        return DaVinci.with(context).load(this.androidVersions.get(row).getUrl()).into(this, row);
    }

    @Override
    public Drawable getBackgroundForPage(final int row, final int column) {
        //nous pouvons spécifier un drawable différent pour le swipe horizontal
        return getBackgroundForRow(row);
    }

    //Le nombre de lignes dans la grille
    @Override
    public int getRowCount() {
        return mRows.size();
    }

    //Le nombre de colonnes par ligne
    @Override
    public int getColumnCount(int rowNum) {
        return mRows.get(rowNum).getColumnCount();
    }

    /**
     * Représentation d'une ligne - Contient une liste de fragments
     */
    private class Row {
        final List columns = new ArrayList();

        public Row(Fragment... fragments) {
            for (Fragment f : fragments) {
                add(f);
            }
        }

        public void add(Fragment f) {
            columns.add(f);
        }

        Fragment getColumn(int i) {
            return columns.get(i);
        }

        public int getColumnCount() {
            return columns.size();
        }
    }

}

Vous pouvez remarquer qu’il est assez rapide et simple de créer une application wear grâce à Emmet et DaVinci, donc à utiliser sans modération !

Vous pouvez retrouver les sources de ce tutoriel sur github

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *