Retrofit – webservices REST

retrofit_long

Une application android a souvent besoin d’une source de données externe, par exemple un site web avec lequel elle va échanger des données.
Prenons l’exemple de l’application youtube, qui va interagir avec les serveurs de google afin de récupérer une liste de vidéos.

Pour ceux qui l’ignorent, l’application n’accède pas directement aux données via des requêtes SQL où que sais-je…

Pour vous expliquer simplement, le serveur met à disposition une liste d’url par lesquels les applications vont pouvoir accéder aux données.
Le format des données échangées se fait le plus souvent en JSON ou en XML.

On dit alors que le serveur fournit un webservice, dans notre cas une API REST.

Retrofit

Retrofit est une librairie permettant de réaliser des appels à des webservices REST sur Android de la façon la plus simple qui soit.

Elle a été développée par le groupe Square, dont je vous ai déjà parlé sur le tutoriel concernant Picasso, la librairie de chargement asynchrone d’images. Vous pouvez d’ailleurs retrouver le github de Retrofit à l’adresse suivante : https://github.com/square/retrofit

Et comme toute bonne librairie, le grand Jake Wharton a participé à son développement 😀

Avant Retrofit

L’utilisation de webservices ne date pas de l’arrivée de Retrofit, si l’on décompose bien les actions à effectuer, nous devions :

  1. Construire et appeler URL
  2. Récupérer le contenu de la page
  3. Analyser / Lire les données

Il existe des composants présents depuis la première version d’Android permettant de réaliser ces actions.
En effet, nous pouvions :

  1. Construire l’url avec des Strings
  2. Utiliser le HttpClient d’Apache afin d’appeler cette URL, puis récupérer le contenu de la page en format String avec EntityUtils
  3. Créer nos parsers
    1. XML avec Dom ou SAX
    2. JSON avec JSONObject

Ce qui une fois terminé nous avait déjà coûté 3 mois de développement et avait complètement démotivé toute l’équipe de développement Android…

Avec Retrofit

Comme toute librairie, il est nécéssaire de l’importer dans notre projet en utilisant Gradle (je vous invite à suivre le tutoriel maitriser-gradle-partie-1 si vous n’êtes pas habitué à l’outil).
Pour cela il vous suffit d’ajouter la ligne suivante à vos dépendances :

compile 'com.squareup.retrofit:retrofit:1.9.0'

Pour la suite du tutoriel, je vais interagir avec l’API de github qui a comme avantage de proposer une version publique, ne nécessitant pas d’authentification.
Retrofit se base sur un fichier de description de notre API REST :

import java.util.List;
import retrofit.http.GET;
import retrofit.http.Path;
import retrofit.http.Query;

public interface GithubService {

    public static final String ENDPOINT = "https://api.github.com";

    @GET("/users/{user}/repos")
    List<Repo> listRepos(@Path("user") String user);

    @GET("/search/repositories")
    List<Repo> searchRepos(@Query("q") String query);
}

Cette interface décrit un webservice contenant 2 méthodes :

  • listRepos affiche la liste des dépots d’un utilisateur
  • searchRepos recherche des dépôts en fonction de mots clés

Regardons maintenant les informations associées à ces fonctions :

En premier lieu, nous devons définir l’url du webservice à appeler :

public static final String ENDPOINT = "https://api.github.com";

Méthode d’appel

Pour chaque fonction, ajouter une annotation définissant la méthode d’appel :

@GET("/users/{user}/repos")
 listRepos(@Path("user") String user);

Les méthodes suivantes sont disponibles :

  • @GET
  • @POST
  • @PUT
  • @DELETE
  • @PATCH

Url de l’appel

Puis l’url de la méthode à appeler :

@GET("/users/{user}/repos")
List listRepos(@Path("user") String user);

Cette url viendra s’ajouter à notre ENDPOINT, il faut donc éviter de mettre l’url complète

Il nous faut ensuite la possibilité de paramétrer nos appels.
Pour cela, deux méthodes nous sont offertes :

Injecter des arguments à notre url

@GET("/users/{user}/repos")
List listRepos(@Path("user") String user);

Par exemple, pour l’utilisateur florent37, l’url suivante sera construite : https://api.github.com/users/florent37/repos

Ajouter des arguments en paramètre de notre url

@GET("/search/repositories")
List searchRepos(@Query("q") String query);

Ainsi, avec le paramètre picasso, l’url suivante sera construite : https://api.github.com/search/repositories?q=picasso

Envoyer des paramètres de formulaire (en POST)

@FormUrlEncoded
@POST("/search/repositories")
List searchRepos(@Field("q") String query);

Interpréter le retour du webservice

Retrofit facilite la lecture des données renvoyées par le webservice. Pour cela, il retournera directement le résultat dans des objets Java.
Par exemple, dans le cas de searchRepos, vous avez pu remarquer que j’ai écrit List avant le nom de la méthode. Ici, Retrofit va me retourner le résultat automatiquement sous forme d’une ArrayList de Repos.

La désérialisation de l’objet est réalisée par GSON, le nom des attributs privés doivent donc être identique aux champs de l’objet JSON afin d’être injectés.

@GET("/search/repositories")
List searchRepos(@Query("q") String query);

Il ne reste qu’à définir l’objet Repo, cliquez sur l’url suivante : https://api.github.com/users/florent37/repos. La page affichée contient le retour du webservice. Je ne vais ici que retenir que les attributs id, name, full_name et html_url, ce qui donne l’objet :

public class Repo {
    private int id;
    private String name;
    private String full_name;
    private String html_url;

    //getters & setters
}

Réaliser nos appels

Il nous faut maintenant fournir notre interface à un objet nommé RestAdapter. C’est lui qui va nous retourner une implémentation de notre webservice :

GithubService githubService = new RestAdapter.Builder()
                .setEndpoint(GithubService.ENDPOINT)
                .build()
                .create(GithubService.class);

Nous pouvons ensuite réaliser nos appels de webservice en utilisant l’objet créé par le RestAdapter, pour cela Retrofit nous offre plusieurs possibilités : synchrone ou asynchrone.

Pensez à ajouter la permission android.permission.INTERNET dans votre manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tutosandroidfrance.RetrofitSample" >

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

    <application
      ...>

    </application>

</manifest>

Appel synchrone

Les appels synchrone permettent d’effectuer une requête.
Il permettent aussi d’obtenir le retour du webservice directement depuis l’appel de la méthode de notre GithubService.

List<Repo> repoList = githubService.listRepos(user);

Attention, ces appels sont bloquants, il est donc interdit de l’effectuer depuis le thread principal, à vous de le placer dans un thread ou une tache asynchrone.

public class MainActivity extends Activity {

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

        new ListReposTask().execute("florent37");
    }

    public void afficherRepos(List<Repo> repos) {
        Toast.makeText(this,"nombre de dépots : "+repos.size(),Toast.LENGTH_SHORT).show();
    }

    class ListReposTask extends AsyncTask<String,Void,List<Repo>>{

        @Override
        protected List<Repo> doInBackground(String...params) {
            GithubService githubService = new RestAdapter.Builder()
                    .setEndpoint(GithubService.ENDPOINT)
                    .build()
                    .create(GithubService.class);

            String user = params[0];
            List<Repo> repoList = githubService.listRepos(user);

            return repoList;
        }

        @Override
        protected void onPostExecute(List<Repo> repos) {
            super.onPostExecute(repos);
            afficherRepos(repos);
        }
    }

}

Appel asynchrone

L’appel asynchrone peux être effectué “depuis” le thread principal de façon assez simple.
Le retour du webservice s’effectue par un callback, un objet dont les méthodes seront appelées suite à la réception de données du webservice :

public class MainActivity extends ActionBarActivity {

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

        GithubService githubService = new RestAdapter.Builder()
                .setEndpoint(GithubService.ENDPOINT)
                .build()
                .create(GithubService.class);

        githubService.listReposAsync("florent37",new Callback<List<Repo>>() {
            @Override
            public void success(List<Repo> repos, Response response) {
                afficherRepos(repos);
            }

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

    public void afficherRepos(List<Repo> repos) {
        Toast.makeText(this,"nombre de dépots : "+repos.size(),Toast.LENGTH_SHORT).show();
    }
}

Pour effectuer les appels de cette façon, il est nécessaire de modifier notre interface GithubService afin d’ ajouter les callbacks :

public interface GithubService {

    ...

    @GET("/users/{user}/repos")
    void listReposAsync(@Path("user") String user, Callback<List<Repo>> callback);

    ...
}

Fonctions supplémentaires de Retrofit

Logger les actions

Retrofit permet d’afficher toutes les actions qu’il effectue ainsi que les données envoyées et reçues. Pour cela il suffit de lui demander d’afficher les Logs :

GithubService githubService = new RestAdapter.Builder()
                .setEndpoint(GithubService.ENDPOINT)
                .setLog(new AndroidLog("retrofit"))
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .build()
                .create(GithubService.class);

Ajouter des headers

Pour certaines requêtes, vous devez jouer avec les headers, voici comment retrofit les utilise :

@Headers("Cache-Control: max-age=640000")
@GET("/users/{user}/repos")
List<Repo> listRepos(@Path("user") String user);

Effectuer la même action à chaque appel

Il est souvent nécessaire d’effectuer une même action à chaque appel de notre webservice (par exemple, une authentification auprès du webservice).
N’ayez crainte, Retrofit le gère aussi ! Il suffit de lui fournir un RequestInterceptor, qui sera appelé avant chaque appel :

GithubService githubService = new RestAdapter.Builder()
                    .setEndpoint(GithubService.ENDPOINT)
                    .setRequestInterceptor(new RequestInterceptor() {
                        @Override
                        public void intercept(RequestFacade request) {
                            //ajoute "baerer: 1234567890" en header de chaque requête
                            request.addHeader("bearer","1234567890");
                        }
                    })
                    .build()
                    .create(GithubService.class);

Gérer les codes d’erreur

Le webservice fournit souvent un code HTTP en cas d’erreur de requête, par exemple 405 Method Not Allowed. Voici comment les récupérer avec Retrofit :

GithubService githubService = new RestAdapter.Builder()
                    .setEndpoint(GithubService.ENDPOINT)
                    .setErrorHandler(new ErrorHandler() {
                        @Override
                        public Throwable handleError(RetrofitError cause) {
                            Response r = cause.getResponse();
                            if (r != null && r.getStatus() == 405) {
                                MainActivity.this.notAllowed();
                            }
                            return cause;
                        }
                    })
                    .build()
                    .create(GithubService.class);

Vous savez maintenant effectuer des appels à un webservice facilement avec la librairie Retrofit, à utiliser sans modération 😉

Vous pouvez trouver un projet d’exemple à l’adresse suivante : https://github.com/florent37/TutosAndroidFrance/tree/master/RetrofitSample

Un commentaire sur “Retrofit – webservices REST

Laisser un commentaire

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