Plus de Groovy avec Android (et un peu de SwissKnife dedans)

Cédric Champeau, concepteur du plugin Android qui m’a sauvé la vie et principal développeur du langage Groovy m’a fait l’honneur de faire une petite remarque technique sur mon billet concernant l’installation du plugin Groovy pour Android. Je me dois donc de la signaler.

Et c’est cool parce que la procédure d’installation du plugin est un poil plus simple. Du coup, dans la deuxième partie du billet, je vous parlerai un peu de l’utilisation de la bibliothèque SwissKnife.

Plus de Groovy avec Android

Résumons. Un nouveau projet AndroidStudio présente deux scripts

build.gradle

 , un général et un à la racine du dossier 

app/

  de votre projet que nous appelleront respectivement

build.gradle

et

app.gradle

.

La manip pour le 

build.gradle

  ne change pas, il faut passer la ligne 

classpath 'com.android.tools.build:gradle:0.11.+'

à

classpath 'com.android.tools.build:gradle:0.12.+'

. J’ai essayé avec une version plus récente (

0.13.2

 ) mais j’obtiens un erreur type

ShortTypeHandling

 . Je sais pas trop d’où ça vient mais vu que ça fonctionne comme un charme avec la version

0.12.x

 , j’ai pas cherché plus loin :D

Dans le fichier

app.gradle

 , les modifications sont plus importantes. Pour rappel, de base, il ressemble à ceci :

apply plugin: 'com.android.application'

android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "augier.fr.test"
        minSdkVersion 19
        targetSdkVersion 20
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

Juste après la première ligne —

apply plugin: 'com.android.application'

— nous allons rajouter le bloc :

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.13.2'
        classpath 'org.codehaus.groovy:gradle-groovy-android-plugin:0.3.5'
    }
}

apply plugin: 'groovyx.grooid.groovy-android'

Et à la toute fin du projet, nous allons rajouter le bloc :

repositories {
	jcenter()
}

dependencies {
	compile 'org.codehaus.groovy:groovy:2.4.1:grooid'
	compile ('org.codehaus.groovy:groovy-json:2.4.0'){
		transitive = false
	}
}

project.androidGroovy {
	options {
		sourceCompatibility = '1.7'
		targetCompatibility = '1.7'
	}
}

Et c’est tout. C’est encore plus simple que dans mon précédent billet qui était, lui basé sur la première version du plugin. Un certain nombre de bugs qui obligeaient à customiser le script de compilation ont été corrigés.

Le gros changement de cette version est que, cette fois, vos scripts Groovy devront être placés dans 

src/main/groovy

  comme l’exige la convention Gradle et plus dans 

src/main/java

  comme c’était le cas dans mon précédent billet. À part ça, rien ne change.

SwissKnife

SwissKnife est une bibliothèque écrite en Groovy — elle ne pourra être utilisée, donc, que dans vos scripts Groovy — et qui vise à simplifier les actions les plus courantes en développement Android. Parmis ces actions très courantes, il y a l’injection de vue. Ça consiste, concrètement, à transformer un bout d’interface graphique écrite en XML en une classe Java — ou ici, Groovy. On injecte la vue dans la classe à l’aide de la méthode

findViewById()

 . Je vais pas vous faire un cours sur les vues dans ce post — déjà parce que ça me soûle et ensuite parce que c’est pas son objet — mais si vous souhaitez en savoir un peu plus sur la chose, vous pouvez consulter ce tuto.

Bref, ce qu’il faut savoir, c’est qu’en développement Android, injecter des vues est une opération que l’on fait très, très souvent. Et taper 

findViewById(R.id.vueDeMerde)

  une fois toutes les deux lignes, bah ça pète vite les burnes. Du coup, des petits malins ont eu l’idée de développer des biblios qui le font à leur place comme les grosses feignasses qu’ils sont (et que je suis aussi).

Et parmis celles-ci, il y a SwissKnife.

Installation

La biblio s’installe très facilement. Concrètement il suffit de rajouter la ligne

compile "com.arasthel:swissknife:1.2.2"

à la fin de la liste des dependancies du bloc juste au-dessus. Comme ceci :

repositories {
	jcenter()
}

dependencies {
	compile 'org.codehaus.groovy:groovy:2.4.1:grooid'
	compile ('org.codehaus.groovy:groovy-json:2.4.0'){
		transitive = false
	}
        /* Ici */
	compile "com.arasthel:swissknife:+"
}

project.androidGroovy {
	options {
		sourceCompatibility = '1.7'
		targetCompatibility = '1.7'
	}
}

Mais avant de compiler, il vous faudra installer la biblio

AndroidSupport

 . Pour ce faire, go

Tools > Android > SDK Manager

et à la fin de la liste, dans la section

Extras

, cochez 

Android Support Repository

  et

Android Support Library

. Ensuite, compilez !

Utilisation

J’ai pas encore bien pris en main la biblio donc je vais me limiter ici à l’utilisation dans le cas nominal : l’utilisation à l’intérieur d’un classe 

Activity

  ou une classe fille.

Concrètement, il suffit d’ajouter

SwissKnife.inject(this)

dans le constructeur et d’annoter toutes vos vues à injecter avec

@InjectView

 . Voici, par exemple la classe d’activité principale générée par AndroidStudio lors d’un nouveau projet :

public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
    } // Seule cette méthode nous intéresse

    /* [...] */
}

Mettons que, dans cette classe, vous souhaitiez afficher une liste toute bête. En Android classique, vous auriez écrit (l’exemple est issu du tutoriel de Vogella) :

public class MyActivity extends Activity
{

    private ListView listView;

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

        getMenuInflater().inflate(R.menu.my, menu);

        this.listView = (ListView) findViewById(R.id.listview);
        String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
                                         "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
                                         "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux",
                                         "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2",
                                         "Android", "iPhone", "WindowsMobile" };

        final ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < values.length; ++i){ list.add(values[i]); }

        final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list);
        listview.setAdapter(adapter);

        return true;
    }
}

Avec le ListView suivant :

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/listView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

C’est pas très beau. Heureusement, avec Groovy et SwissKnife, nous allons pouvoir considérablement réduire le code :

public class MyActivity extends Activity
{
    // La vue est directement injectée ici
    @InjectView ListView listView

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState)
        contentView = R.layout.activity_my
        // On prévient SwissKnife qu'il y a des choses à injecter ici
        SwissKnife.inject(this)

        menuInflater.inflate(R.menu.my, menu)

        /* Nous n'avons plus besoin de ceci
         * this.listView = (ListView) findViewById(R.id.listview)
         */

        // Ceci fait partie des beautés du Groovy ;)
        def list = ["Android", "iPhone", "WindowsMobile",
                    "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
                    "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux",
                    "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2",
                    "Android", "iPhone", "WindowsMobile"]

        final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list)
        listview.setAdapter(adapter)

        return true
    }
}

On est d’accord que c’est plus joli, non ? Plus de boucle affreuse pour convertir un tableau en

ArrayList

 , plus de

findViewById()

 …

SwissKnife possède plusieurs autres annotations très sympas comme celle-ci, essentiellement concernant des réaction à des évènements comme le clic ou le long-clic. Ça permet de remplacer ceci :

listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){
    public void onItemClick(AdapterView<?> parent, final View view, int position, long id)
    {
        Toast.makeText(this, "Button clicked", Toast.LENGTH_SHORT).show();
    }
});

Par ceci :

@OnItemClick(R.id.listView)
public void onItemClick(int position)
{
    Toast.makeText(this, "Button clicked", Toast.LENGTH_SHORT).show()
}

Y’a pas photo, si ?

Déjà 4 avis pertinents dans Plus de Groovy avec Android (et un peu de SwissKnife dedans)

  • Super :)

    Pour que ce soit encore plus Groovy, quelques remarques: tu peux utiliser Groovy 2.4.1 (au lieu de 2.4.0-rc-2). Il te reste encore quelques point virgules ;) Sinon, tu peux écrire:

    contentView = R.id.foo

    Au lieu de setContentView(R.id.foo). Dans le même genre, je préfère:

    menuInflater.inflate(…)

    au lieu de:

    getMenuInflater().inflate(…)

    Tu peux aussi mettre:

    def list = […]

    Au lieu de ArrayList list = …

    Mais bon c’est plutôt pas mal tout ça :)

  • Maciej Górski
    I would not go with `contentView = R.layout.foo`, because (in this case) familiarity beats verbosity.
    `setContentView(R.layout.foo)` is only 2 characters longer, but will be way faster to recognize in code for any Android developer.

Les commentaires sont fermés.