Skip to content

Commit 3fba95d

Browse files
author
Johan Poirier
committed
New article on Square libs for Android
1 parent 328aa6e commit 3fba95d

File tree

2 files changed

+364
-0
lines changed

2 files changed

+364
-0
lines changed
Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
---
2+
layout: post
3+
title: Les libs Android de Square
4+
author: johanpoirier
5+
tags: [android, square, libraries, bus, injection, rest, http]
6+
published: false
7+
---
8+
9+
Ayant eu la chance d'aller au [Devoxx](http://devoxx.com) encore une fois cette année, j'ai pu assister à la conférence de [Jake Wharton](https://github.com/JakeWharton) (créateur de [ActionBarSherlock](http://actionbarsherlock.com/)) intitulée [Bootstrapping Android Apps with Open Source](http://devoxx.com/display/DV12/Bootstrapping+Android+Apps+with+Open+Source). Il s'agissait en fait d'une présentation de toutes les librairies développées et utilisées par [Square](https://github.com/square) pour développer leur [application de paiement par mobile](https://squareup.com/).
10+
Le principal message que Jake a voulu faire passer était de nous inciter à partager nos projets ou nos divers développements en Open Source si cela peut avoir un intérêt pour la communauté. Square s'appuyant très largement sur des projets Open Source, ils se doivent de partager à leur tour leur travail.
11+
Nous allons donc voir quelque unes de ces librairies illustrées dans une petite applicaton Android : Dagger, Otto et Retrofit.
12+
13+
<p class="center">
14+
<img src="/public/img/2012-11-28-square-libs/square.png" border="0" />
15+
</p>
16+
17+
18+
## [Dagger](http://square.github.com/dagger/) : l'injection de dépendance
19+
20+
Dagger se veut comme un successeur de Guice dont le créateur [Bob Lee](https://twitter.com/crazybob) est justement le CTO de Square. Il a voulu créer un framework d'injection de dépendances rapide et moderne qui fonctionne aussi bien en java "classique" que sur Android avec sa JVM Dalvik adapté au mobile.
21+
22+
A l'instar d'[AndroidAnnotations](http://androidannotations.org/), Dagger s'appuie sur la génération de code à la compilation (JSR 269 : Annotation Processing). Pour chaque classe gérée par Dagger, une classe est créee contenant toute la logique d'injection en se basant sur le graph d'objet. C'est cette classe qui s'occupera d'invoquer les constructeurse t injecter les variables annotées en @Inject (JSR 330).
23+
24+
Dagger se passe donc de tout usage de réflexion contrairement à Guice (et sa version pour Android : RoboGuice). Et ceci est une bonne chose pour nos applications Android car le principal défaut de Guice était bien le temps de construire le graph au démarrage de l'application.
25+
26+
Place à l'illustration de la librairie par un peu de code :
27+
28+
### Les principes
29+
30+
{% highlight java %}
31+
// Main activity of the app
32+
public class MainActivity extends Activity {
33+
34+
@Inject
35+
UserService userService;
36+
37+
@Override
38+
public void onCreate(Bundle savedInstanceState) {
39+
super.onCreate(savedInstanceState);
40+
DaggerModule.getObjectGraph().inject(this);
41+
}
42+
43+
...
44+
}
45+
{% endhighlight %}
46+
47+
Cet exemple nous montre 2 choses :
48+
- l'annotation @Inject de mon service gérant mes utilisateurs
49+
- l'injection de la classe à la création de l'activité
50+
51+
Pour satisfaire les dépendances, Dagger construit les objets via leurs constructeurs sans argument annotés par @Inject ou bien via des @Provides si la première solution n'est pas possible. Les méthodes annotées en @Provides doivent appartenir à un @Module :
52+
53+
{% highlight java %}
54+
// Dagger module for the app
55+
@Module(entryPoints = { MainActivity.class })
56+
public class DaggerModule {
57+
58+
private static ObjectGraph graph;
59+
60+
@Provides
61+
public UserService provideUserService() {
62+
return new UserService();
63+
}
64+
}
65+
{% endhighlight %}
66+
67+
Ce module est indispensable pour construire le graph d'objet :
68+
69+
{% highlight java %}
70+
// graph construction from the module and its entry points
71+
ObjectGraph objectGraph = ObjectGraph.create(new DaggerModule());
72+
{% endhighlight %}
73+
74+
Grâce à ce graph d'objet, nous pouvons injecter les classes gérées par Dagger :
75+
76+
{% highlight java %}
77+
// self inject
78+
objectGraph.inject(this);
79+
{% endhighlight %}
80+
81+
Il y a plein d'autres concepts intéressants dans Dagger comme la surcharge de Module à des fins de tests, ou encore les @Singleton et l'injection statique.
82+
83+
### En pratique sur Android
84+
85+
Pour éviter d'appeler l'ObjectGraph pour injecter mes activités dans le onCreate, j'ai créé une DaggerActivity dont toutes mes activités héritent :
86+
87+
{% highlight java %}
88+
// Parent of all activities
89+
public abstract class DaggerActivity extends Activity {
90+
91+
protected void onCreate(android.os.Bundle savedInstanceState) {
92+
super.onCreate(savedInstanceState);
93+
DaggerModule.getObjectGraph().inject(this);
94+
}
95+
}
96+
{% endhighlight %}
97+
98+
Le module de mon application s'occupe de fournir les instances via des @Provides et sert de point d'entrée à toute mon application pour fournir le graph d'objet :
99+
100+
{% highlight java %}
101+
// the entry point is where the graph begins
102+
@Module(entryPoints = { MainActivity.class })
103+
public class DaggerModule {
104+
105+
private static ObjectGraph graph;
106+
107+
@Provides
108+
@Singleton
109+
public MyService provideUserService() {
110+
return new UserService();
111+
}
112+
113+
@Provides
114+
@Singleton
115+
public Bus provideBus() {
116+
// our event bus running on any thread
117+
return new Bus(ThreadEnforcer.ANY);
118+
}
119+
120+
// give access to the graph in the entire app
121+
public static ObjectGraph getObjectGraph() {
122+
if(graph == null) {
123+
graph = ObjectGraph.create(new DaggerModule());
124+
}
125+
return graph;
126+
}
127+
}
128+
{% endhighlight %}
129+
130+
J'ai réalisé une petit application de test disponible sur Github : [squarelibs-android-demo](https://github.com/johanpoirier/squarelibs-android-demo). Au lancement de l'application, c'est immédiat, pas de temps de création du graph décelable par l'utilisateur.
131+
132+
133+
## [Otto](http://square.github.com/otto/) : le bus d'évènements
134+
135+
Otto est un bus d'évènements permettant de découpler les différentes parties de nos applications Android. Ce projet est un fork de [Guava](http://code.google.com/p/guava-libraries/) modifié et spécialisé pour Android.
136+
137+
### Le principe
138+
139+
Le principe est simple.
140+
141+
Pour publier un évènement, il faut poster un évènement sur le bus :
142+
143+
{% highlight java %}
144+
// AwesomeEvent could be anything
145+
bus.publish(new AwesomeEvent());
146+
{% endhighlight %}
147+
148+
Pour s'abonner à un évènement, il faut s'enregistrer auprès du bus :
149+
150+
{% highlight java %}
151+
// the bus need to know that you're listening
152+
bus.register(this);
153+
{% endhighlight %}
154+
155+
et être prêt à recevoir l'évènement :
156+
157+
{% highlight java %}
158+
// subscription to the AwesomeEvent(s)
159+
@Subscribe
160+
public void awesomeEventOccured(AwesomeEvent event) {
161+
Log.d("AwesomeApp", event.getAwesomeMessage());
162+
}
163+
{% endhighlight %}
164+
165+
Pour ne plus recevoir les évènements, il suffit de se désenregistrer :
166+
167+
{% highlight java %}
168+
// tell the bus we don't care anymore
169+
bus.unregister(this);
170+
{% endhighlight %}
171+
172+
Enfin, il est possible de fournir une valeur dès l'enregistrement sur le bus via des @Produce. Cela peut permettre ainsi de fournir le dernier évènement publié sur le bus au client qui vient de s'enregistrer.
173+
174+
{% highlight java %}
175+
// useful when you need to know what was going on before you listened
176+
@Produce
177+
public AwesomeEvent produceAwesomeEvent() {
178+
// lastAwesomeEvent must exist
179+
return new AwesomeEvent(this.lastAwesomeEvent);
180+
}
181+
{% endhighlight %}
182+
183+
Les producteurs comme les clients souscripteurs doivent s'enregistrer sur le bus.
184+
185+
186+
### En pratique sur Android
187+
188+
On a vu avec Dagger que le module de l'application peut produire des instances à injecter dans les classes gérées par celui-ci : c'est exactement ce qu'il nous faut pour fournir une intance du bus à tous ceux qui en ont besoin :
189+
190+
{% highlight java %}
191+
// take the bus !
192+
@Module(entryPoints = { MainActivity.class })
193+
public class DaggerModule {
194+
195+
...
196+
197+
@Provides
198+
@Singleton
199+
public Bus provideBus() {
200+
// our event bus running on any thread
201+
return new Bus(ThreadEnforcer.ANY);
202+
}
203+
204+
...
205+
}
206+
{% endhighlight %}
207+
208+
Nous allons également modifier notre DaggerActivity :
209+
210+
{% highlight java %}
211+
// bus injection and registration
212+
public abstract class DaggerActivity extends Activity {
213+
214+
@Inject
215+
protected Bus bus;
216+
217+
protected void onCreate(android.os.Bundle savedInstanceState) {
218+
super.onCreate(savedInstanceState);
219+
220+
// Dagger will inject instances
221+
DaggerModule.getObjectGraph().inject(this);
222+
223+
// The activity will register itself to the Otto bus
224+
bus.register(this);
225+
}
226+
}
227+
{% endhighlight %}
228+
229+
Ainsi, chaque activité a accès au bus.
230+
Pour l'exemple, mon activité va écouter les évènements liés à l'activité WiFi du téléphone :
231+
{% highlight java %}
232+
// event subscription
233+
public class MainActivity extends Activity {
234+
235+
...
236+
237+
@Subscribe
238+
public void displayNetworks(final NetworksAvailableEvent event) {
239+
// event is coming from the background, requestiing UI thread
240+
runOnUiThread(new Runnable() {
241+
@Override
242+
public void run() {
243+
networkListAdapter.changeData(event.getNetworks());
244+
}
245+
});
246+
}
247+
248+
...
249+
}
250+
{% endhighlight %}
251+
252+
L'évènement NetworksAvailableEvent est produit par un BroadcastReceiver qui tourne en background :
253+
254+
{% highlight java %}
255+
// event post on the bus
256+
public class WifiInfoReceiver extends DaggerBroadcastReceiver {
257+
258+
private List<ScanResult> scanResults;
259+
private WifiManager wifiManager;
260+
261+
@Inject
262+
protected Bus bus;
263+
264+
@Override
265+
public void onReceive(Context context, Intent intent) {
266+
super.onReceive(context, intent);
267+
wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
268+
269+
DaggerModule.getObjectGraph().inject(this);
270+
271+
// scan results available : post event to the bus to display on the main activity
272+
if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
273+
scanResults = wifiManager.getScanResults();
274+
bus.post(new NetworksAvailableEvent(scanResults));
275+
}
276+
// the wifi state changed : display it on the main activity
277+
else if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
278+
bus.post(new WifiStateChangeEvent(wifiManager.getWifiState()));
279+
}
280+
}
281+
}
282+
{% endhighlight %}
283+
284+
285+
## [Retrofit](https://github.com/square/retrofit) : une interface REST
286+
287+
Retrofit est un client REST pour Android, dans le même esprit que Spring Android [RestTemplate](http://static.springsource.org/spring-android/docs/1.0.x/reference/html/rest-template.html).
288+
289+
Retrofit est très léger : 52 Ko contre 252 Ko pour RestTemplate.
290+
291+
### Le principe
292+
293+
Encore une fois très simple, il suffit d'écrire nos interfaces via les annotations @GET, @POST, @DELETE, @PUT :
294+
295+
{% highlight java %}
296+
// our rest client for the github api
297+
public interface Github {
298+
299+
@GET("orgs/square/repos")
300+
List<Repo> getSquareRepos();
301+
302+
@GET("orgs/square")
303+
Org getSquare();
304+
}
305+
{% endhighlight %}
306+
307+
Les classes Repo et Org sont de simples POJOs qui ont les mêmes attributs (même partiellement) que ceux de l'API appelée.
308+
309+
Pour utliser mon interface, il nous faut utiliser un RestAdapater :
310+
311+
{% highlight java %}
312+
// the RestAdapter builder is nice
313+
restAdapter = new RestAdapter.Builder()
314+
.setServer(new Server("https://api.github.com"))
315+
.setClient(new DefaultHttpClient())
316+
.setConverter(new GsonConverter(new Gson()))
317+
.build();
318+
{% endhighlight %}
319+
320+
et de l'appeler avec notre interface :
321+
322+
{% highlight java %}
323+
// rest client instanciation
324+
Github githubApi = restAdapter.create(Github.class);
325+
List<Repo> repos = githubApi.getSquareRepos();
326+
{% endhighlight %}
327+
328+
Et voilà !
329+
330+
331+
### En pratique sur Android
332+
333+
Nous allons encore une fois utiliser notre module Dagger comme point d'entrée de l'application pour avoir accès à notre RestAdapter dans toute l'application :
334+
335+
{% highlight java %}
336+
// Dagger + Retrofit : awesome
337+
@Module(entryPoints = { Main.class })
338+
public class DaggerModule {
339+
340+
private static ObjectGraph graph;
341+
342+
...
343+
344+
@Provides
345+
@Singleton
346+
public RestAdapter getRestAdapter() {
347+
// give access to the rest api to the entire app
348+
return new RestAdapter.Builder()
349+
.setServer(new Server("https://api.github.com"))
350+
.setClient(new DefaultHttpClient())
351+
.setConverter(new GsonConverter(new Gson()))
352+
.build();
353+
}
354+
}
355+
{% endhighlight %}
356+
357+
Nous utilisons Gson comme librairie de sérialisation/désérialisation et le client HTTP par défaut d'Android. A ce propos, Square propose une autre librairie nommée [OkHttp](https://github.com/square/okhttp) qui est un client HTTP+SPDY pour Android. OkHttp permet de pouvoir compter sur le même client http sur toutes les versions d'Android et ne pas dépendre de la version d'Android. Il faudrait le tester et surtout attendre une version plus finalisée.
358+
359+
360+
## Conclusion
361+
362+
Dagger, Otto et Retrofit sont de petites librairies, encore jeunes mais très prometteuses. Elles sont parfaitement adpatées à un contexte d'utilisation mobile car elles ont été pensé pour. Si vous ne devez en retenir qu'une, je vous conseille Otto qui est un merveilleux petit outil pour découpler les composants de votre application.
363+
364+
Par ailleurs, [Pierre-Yves Ricau](https://github.com/pyricau), le créateur du projet AndroidAnnotations, a fourni un exemple d'intégration de Dagger, Otto et AndroidAnnotations sur Github : [CleanAndroidCode](https://github.com/pyricau/CleanAndroidCode).
49.7 KB
Loading

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy