Dans cet article vous trouverez une liste de dix bibliothèques utilisées dans certaines applications Android développées à Xebia et qui sont actuellement en production. Nous pensons que l’utilisation de ces bibliothèques doit être étudiée avant et durant un projet afin de gagner du temps, de la lisibilité et de la maintenabilité. L’article est articulé autour de questions récurrentes lors du développement d’une application Android : à chaque question, nous avons fait correspondre une bibliothèque.
Comment consommer facilement une API REST ?
Retrofit
Retrofit permet de transformer une API REST en une interface Java puis de la consommer.
public interface HackerNewsApiService { @GET("/v0/item/{id}.json") News getNews(@Path("id") String id); }
Un adapteur permet de générer une implémentation de cette interface.
RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://hacker-news.firebaseio.com") .build(); HackerNewsApiService service = restAdapter.create(HackerNewsApiService.class);
Puis les méthodes de l’adapteur peuvent être appelées pour générer un appel réseau vers l’API.
News news = service.getNews("8863");
Les annotations permettent de manipuler la requête :
- définir le type de la requête (GET, POST, PUT, DELETE and HEAD),
- manipuler l’URL (Path, Query, QueryMap),
- définir le corp (Body),
- de construire une requête avec les paramètres encodés ou multi-part,
- de gérer les en-têtes (Headers).
Les appels peuvent être fait de façon synchrone (blocage de l’exécution) si la méthode retourne un type :
@GET("/v0/item/{id}.json") News getNews(@Path("id") String id);
Ou asynchrone si la méthode prend en paramètre un Callback.
@GET("/v0/item/{id}.json") void getNews(@Path("id") String id, Callback<News> callback);
Le callback est exécuté sur le thread principal.
Documentation : Retrofit
Comment simplifier les interactions entre composants ?
EventBus
EventBus est un bus d’évènement spécialement conçu pour Android. Il facilite le découplage entre les composants d’une application par l’utilisation de messages au lieu des "classiques" callbacks. Il est possible de choisir si l’on souhaite traiter le message sur le thread UI ou sur un thread de background.
public class SampleActivity extends Activity { private static final EventBus BUS = EventBus.getDefault(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.home_activity); BUS.register(this); BUS.post(new SyncEvent()); BUS.post(new AsyncEvent()); } public void onEventMainThread(SyncEvent syncEvent){ // TODO s'exécute sur le main thread } public void onEventBackgroundThread(AsyncEvent asyncEvent){ // TODO s'exécute sur un thread de background } @Override protected void onDestroy() { BUS.unregister(this); super.onDestroy(); } public static class SyncEvent {}; public static class AsyncEvent {}; }
Documentation : EventBus
Comment manipuler efficacement des données JSON ?
Jackson
Jackson est un projet OpenSource qui permet de manipuler des données au format JSON efficacement. L’intégration de Jackson dans Retrofit est triviale : retrofit-converters. Au final, pour l’appel retrofit suivant :
public interface HackerNewsApiService { @GET("/v0/item/{id}.json") News getNews(@Path("id") String id); }
Le JSON associé est le suivant :
{ "by": "dhouston", "id": 8863, "title": "My YC app: Dropbox - Throw away your USB drive" }
Et la classe java correspondante :
public class News { public String by; public int id; public String title; }
Documentation : Jackson
Quel client HTTP est compatible avec toutes les version d’Android ?
OkHttp
Un des clients les plus efficaces est OkHttp. Les fonctionnalités principales de cette bibliothèque sont les suivantes :
- le support SPDY permet à toutes les requêtes de partager le même socket (si le host est le même);
- un pool de connection permet de réduire le temps de connexion au serveur;
- les réponses transitent sous la forme de packet gzip et sont cachées automatiquement.
OkHttp est résiliant quand le réseau n’est pas performant. Si les services consommés ont plusieurs adresses IP, OkHttp peut alterner en fonction de la performance de chacune. De plus OkHttp initialise ses nouvelles connections avec les fonctionnalités TLS sinon il utilisera le protocole SSLv3.
Exemple de récupération d’un contenu à partir d’une URL.
OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException { Request req = new Request.Builder() .url(url) .build(); Response resp = client.newCall(request).execute(); return response.body().string(); }
Utiliser OkHttp avec Retrofit
OkHttp peut être utilisé avec Retrofit.
RestAdapter restAdapter = new RestAdapter.Builder() .setClient(new OkHttpClient()) .setEndpoint("https://hacker-news.firebaseio.com") .build(); HackerNewsApiService service = restAdapter.create(HackerNewsApiService.class);
Documentation : OkHttp
Comment éviter les nombreux findViewById ?
Butter Knife
Butter Knife est une bibliothèque d’injection de vues. Elle repose sur le principe d’annotation processing afin de générer du code boilerplate pour vous. Fini les findViewById et les inner-classes pour les listeners ! De plus le code généré est facilement debuggable.
Pour injecter une vue, il suffit d’utiliser l’annotation @InjectView :
public class HomeActivity extends Activity { @InjectView(R.id.title) TextView title; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.inject(this); title.setText("Hello world!"); } }
Pour ajouter un OnClickListener, il suffit d’annoter une méthode avec @OnClick :
public class HomeActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity_with_button); ButterKnife.inject(this); } @OnClick public void onButtonClicked(Button button) { button.setText("Clicked!"); } }
Cette librairie propose beaucoup d’autres annotations : @OnItemSelected, @OnFocusChanged, @OnLongClick…
Pour finir Butter Knife ne se limite pas qu’aux activités :
public class HomeFragment extends Fragment { @InjectView(R.id.title) TextView title; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.simple_fragment, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { ButterKnife.inject(this, view); title.setText("Hello world!"); } }
Documentation : Butter Knife
Comment charger et manipuler des images efficacement ?
Picasso
Picasso permet de gérer le téléchargement et le cache d’image très simplement.
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Le recyclage, l’annulation du téléchargement et le cache sont gérés automatiquement. Les transformations sur les images sont optimisées et plus simple à faire.
Documentation : Picasso
Comment gérer la sauvegarde et la restauration des objets lors d’un changement d’état ?
IcePick
IcePick utilise des annotations pour générer le code qui permet d’ajouter un objet au Bundle puis de le restaurer.
class ExampleActivity extends Activity { @Icicle String name; // Sauvegarde et restauration automatique du champ. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Icepick.restoreInstanceState(this, savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } }
Documentation : IcePick
Comment écrire les logs sur la sortie standard en développement et sur un service externe en production ?
Timber
Timber est un outils basé sur la classe Log d’Android configurable par environnement à travers une simple API.
La classe Tree permet de définir le comportement des logs.
Par exemple, pour envoyer un log sur Crashlytics :
private static class CrashReportingTree extends Timber.HollowTree { @Override public void e(String message, Object... args) { Crashlytics.log(Log.ERROR, String.format(message, args)); } @Override public void e(Throwable t, String message, Object... args) { e(message, args); Crashlytics.logException(t); } }
Pour commencer à utiliser Timber, il faut appeler la méthode Timber.plant avec une classe Tree en paramètre dans le méthode onCreate() de votre application.
if (BuildConfig.DEBUG) { Timber.plant(new DebugTree()); } else { Timber.plant(new CrashReportingTree()); }
L’implémentation DebugTree ajoutera automatiquement le nom de la classe et l’utilisera comme tag.
Documentation : Timber
Comment définir efficacement un ContentProvider ?
Schematic
Cette bibliothèque génère automatiquement un ContentProvider à l’aide d’annotations. Le content provider généré utilise une base de données SQLite pour la persistence.
Il faut d’abord définir les colonnes de la table :
public interface NewsColumns { @DataType(INTEGER) @PrimaryKey @AutoIncrement String _ID = "_id"; @DataType(TEXT) @NotNull String title = "title"; }
Puis créer la base de données associée :
@Database(version = NewsCollectionDatabase.VERSION) public final class NewsCollectionDatabase { public static final int VERSION = 1; @Table(NewsColumns.class) public static final String NEWS_COLLECTION = "newsCollection"; }
Pour finir il reste à déclarer le content provider :
@ContentProvider(authority = NewsCollectionProvider.AUTHORITY, database = NewsCollectionDatabase.class) public final class NewsCollectionProvider { public static final String AUTHORITY = "fr.xebia.android.NewsCollectionProvider"; @TableEndpoint(table = NewsCollectionDatabase.NEWS_COLLECTION) public static class NewsCollection { @ContentUri( path = Path.NewsCollection, type = "vnd.android.cursor.dir/news", defaultSort = NewsColumns.TITLE + " ASC") public static final Uri LISTS = Uri.parse("content://" + AUTHORITY + "/newsCollection") }
Documentation : Schematic
Comment focaliser ses développements sur les classes essentielles de son application ?
Dagger
Dagger est un framework d’injection de dépendances qui s’articule autour de la JSR-330. Pour rappel l’injection de dépendances favorise l’écriture de code modulable, réutilisable et surtout testable ! Contrairement à Guice la vérification des dépendances se fait à la compilation, de plus Dagger utilise très peu de reflexion dans sa version 1.x.
public class HomeActivity extends Activity { @Inject HackerNewsApiService hackerNewsApiService; ObjectGraph objectGraph; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.home_activity); objectGraph = ObjectGraph.create(new HomeActivityModule()); objectGraph.inject(this); // TODO use the hacker news service } @Module(injects = HomeActivity.class) public static class HomeActivityModule { @Singleton public HackerNewsApiService provideHackerNewsApiService(){ return new RestAdapter.Builder().setEndpoint("https://hacker-news.firebaseio.com") .build().create(HackerNewsApiService.class); } } }
Documentation : Dagger
La liste présentée ci-dessus n’est pas exhaustive et nous nous efforçons de toujours rechercher de nouvelles bibliothèques intéressantes pour nos développements. Si vous pensez à une bibliothèque qui n’est pas citée dans cet article, n’hésitez pas à poster un commentaire.
Benjamin Lacroix / https://twitter.com/benjlacroix
Thomas Guerin / https://twitter.com/Tom404_