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
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)
Les commentaires sont fermés.
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
contentView = R.id.foo
, c’est vrai, j’avais oublié que Groovy était à l’aise avec les getters et les settersÀ propos de
def list = […]
en général, je préfère typer statiquement les variables autant que possible ; sauf quand le type est vraiment évident ou que c’est une variable très temporaire. Ici, il s’agit d’un type générique. Et comme j’ai compris qu’en Groovy[]
pouvait représenter aussi bien uneMap
qu’uneArrayList
de n’importe quoi, c’est plus une sécurité contre mes propres erreurs en fait :pCeci dit, c’est vrai que, vu que j’initialise la liste tout de suite, y’a pas vraiment d’ambiguïtés.
Merci pour le retour en tout cas. Il est possible que je continue d’écrire des articles sur Groovy et Android à l’avenir pour faire état de mes avancés sur le projet. J’espère arriver à une application qui puisse sortir sur F-droid.
`setContentView(R.layout.foo)` is only 2 characters longer, but will be way faster to recognize in code for any Android developer.