1. Rénovation
1.1. Qu'est-ce que Retrofit?
Retrofit est un client REST pour Java et Android. Il facilite l'obtention et le chargement de JSON (ou d'autres donnĂ©es structurĂ©es) via un service Web basĂ© sur REST. Dans Retrofit, vous configurez le convertisseur utilisĂ© pour sĂ©rialiser les donnĂ©es. GSon est gĂ©nĂ©ralement utilisĂ© pour JSON, mais vous pouvez ajouter vos propres convertisseurs pour traiter XML ou d'autres protocoles. Retrofit utilise la bibliothĂšque OkHttp pour les requĂȘtes HTTP.
Vous pouvez crĂ©er des objets Java basĂ©s sur JSON Ă l'aide de l'outil suivant: www.jsonschema2pojo.org Cela peut ĂȘtre utile pour crĂ©er des structures de donnĂ©es Java complexes Ă partir de JSON existant.
1.2. Utilisation de Retrofit
Pour travailler avec Retrofit, vous aurez besoin des trois classes suivantes:
- Classe de modÚle utilisée comme modÚle JSON
- Interfaces qui identifient les opérations HTTP possibles
- La classe Retrofit.Builder est une instance qui utilise l'interface Builder et l'API pour définir une définition de point de terminaison URL pour les opérations HTTP.
Chaque méthode d'interface représente l'un des appels d'API possibles. Il doit avoir une annotation HTTP (GET, POST, etc.) pour indiquer le type de demande et l'URL relative. La valeur de retour termine la réponse dans l'objet Call avec le type de résultat attendu.
@GET("users") Call<List<User>> getUsers();
Vous pouvez utiliser les blocs de remplacement et les paramĂštres de requĂȘte pour configurer l'URL. Le bloc de remplacement est ajoutĂ© Ă l'URL relative Ă l'aide de {}. En utilisant l'annotation @ Path pour un paramĂštre de mĂ©thode, la valeur de ce paramĂštre est liĂ©e Ă un bloc de remplacement spĂ©cifique.
@GET("users/{name}/commits") Call<List<Commit>> getCommitsByName(@Path("name") String name);
Les paramĂštres de requĂȘte sont ajoutĂ©s Ă l'aide de l'annotation @ Query au paramĂštre de mĂ©thode. Ils sont automatiquement ajoutĂ©s Ă la fin de l'URL.
@GET("users") Call<User> getUserById(@Query("id") Integer id);
L'annotation @ Body du paramÚtre de méthode indique à Retrofit d'utiliser l'objet comme corps de demande à appeler.
@POST("users") Call<User> postUser(@Body User user)
2. Prérequis
Les exemples suivants utilisent l'IDE Eclipse avec le systÚme de génération Gradle.
Cet exercice suppose que vous
maĂźtrisez Gradle et que vous
utilisez Gradle avec Eclipse .
D'autres environnements de dĂ©veloppement, tels que Visual Studio Code ou IntelliJ, font de mĂȘme, vous pouvez donc utiliser votre outil prĂ©fĂ©rĂ©.
3. Exercice: premier client de rénovation
Dans cet exercice, vous allez créer un client REST autonome. Les réponses sont générées par le serveur Mock.
3.1. Création et mise en place d'un projet
Créez un nouveau projet Gradle nommé com.vogella.retrofitgerrit. Ajoutez un nouveau package à src / main / java nommé com.vogella.retrofitgerrit.
Ajoutez les dépendances suivantes au fichier build.gradle.
// retrofit implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' // Junit testImplementation("org.junit.jupiter:junit-jupiter-api:5.0.0") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0") // to run JUnit 3/4 tests: testImplementation("junit:junit:4.12") testRuntime("org.junit.vintage:junit-vintage-engine:4.12.0")
3.2. Définir l'API et l'adaptateur de modification
Dans la réponse JSON de Gerrit, nous ne nous intéressons qu'à la question des changements. Par conséquent, créez la classe de données suivante dans le package par défaut précédemment ajouté.
package com.vogella.java.retrofitgerrit; public class Change { String subject; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } }
Définissez l'API REST pour Retrofit via l'interface suivante.
package com.vogella.java.retrofitgerrit; import java.util.List; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; public interface GerritAPI { @GET("changes/") Call<List<Change>> loadChanges(@Query("q") String status); }
Créez la classe de contrÎleur suivante. Cette classe crée un client Retrofit, appelle l'API Gerrit et traite le résultat (imprime le résultat de l'appel à la console).
package com.vogella.java.retrofitgerrit; import java.util.List; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class Controller implements Callback<List<Change>> { static final String BASE_URL = "https://git.eclipse.org/r/"; public void start() { Gson gson = new GsonBuilder() .setLenient() .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); GerritAPI gerritAPI = retrofit.create(GerritAPI.class); Call<List<Change>> call = gerritAPI.loadChanges("status:open"); call.enqueue(this); } @Override public void onResponse(Call<List<Change>> call, Response<List<Change>> response) { if(response.isSuccessful()) { List<Change> changesList = response.body(); changesList.forEach(change -> System.out.println(change.subject)); } else { System.out.println(response.errorBody()); } } @Override public void onFailure(Call<List<Change>> call, Throwable t) { t.printStackTrace(); } }
Créez une classe avec une méthode principale pour démarrer le contrÎleur.
package com.vogella.java.retrofitgerrit; public class Main { public static void main(String[] args) { Controller controller = new Controller(); controller.start(); } }
4. Convertisseurs et adaptateurs modernisés
4.1. Convertisseurs de rattrapage
La modification peut ĂȘtre configurĂ©e pour utiliser un convertisseur spĂ©cifique. Ce convertisseur gĂšre la (dĂ©) sĂ©rialisation des donnĂ©es. Plusieurs convertisseurs sont dĂ©jĂ disponibles pour diffĂ©rents formats de sĂ©rialisation.
- Pour convertir en JSON et vice versa:
- Gson: com.squareup.retrofit: converter-gson
- Jackson: com.squareup.retrofit: convertisseur-jackson
- Moshi: com.squareup.retrofit: convertisseur-moshi
- Pour convertir en tampons de protocole et vice versa:
- Protobuf: com.squareup.retrofit: convertisseur-protobuf
- Fil: com.squareup.retrofit: convertisseur-fil
- Pour convertir en XML et vice versa:
- XML simple: com.squareup.retrofit: converter-simplexml
En plus des convertisseurs répertoriés, vous pouvez également créer le vÎtre pour traiter d'autres protocoles en étendant la classe Converter.Factory.
4.2. Adaptateurs d'adaptation
La mise Ă niveau peut Ă©galement ĂȘtre Ă©tendue avec des adaptateurs pour l'interfaçage avec d'autres bibliothĂšques telles que RxJava 2.x, Java 8 et Guava.
Vous trouverez un aperçu des adaptateurs disponibles sur Github
square / retrofit / retrofit-adapters / .
Par exemple, l'adaptateur RxJava 2.x peut ĂȘtre connectĂ© Ă l'aide de Gradle:
compile 'com.squareup.retrofit2:adapter-rxjava2:latest.version'
ou en utilisant Apache Maven:
<dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>adapter-rxjava2</artifactId> <version>latest.version</version> </dependency>
Pour ajouter un adaptateur, vous devez utiliser la méthode retrofit2.Retrofit.Builder.addCallAdapterFactory (Factory).
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();
Ă l'aide de cet adaptateur, les interfaces Retrofit peuvent renvoyer des types RxJava 2.x, par exemple, Observable, Flowable ou Single, etc.
@GET("users") Observable<List<User>> getUsers();
5. Authentification ultérieure
Retrofit prend en charge les appels d'API qui nĂ©cessitent une authentification. L'authentification peut ĂȘtre effectuĂ©e Ă l'aide d'un nom d'utilisateur et d'un mot de passe (authentification Http Basic) ou d'un jeton d'API.
Il existe deux façons de gĂ©rer l'authentification. La premiĂšre mĂ©thode consiste Ă gĂ©rer l'en-tĂȘte de demande Ă l'aide d'annotations. Une autre façon consiste Ă utiliser un intercepteur OkHttp pour cela.
5.1. Authentification avec annotations
Supposons que vous souhaitiez demander des informations sur un utilisateur pour lequel l'authentification est requise. Vous pouvez le faire en ajoutant un nouveau paramÚtre à la définition de l'API, par exemple:
@GET("user") Call<UserDetails> getUserDetails(@Header("Authorization") String credentials)
En utilisant l'annotation @ Header ("Authorization"), vous indiquez Ă Retrofit d'ajouter l'en-tĂȘte Authorization Ă la demande avec la valeur que vous transmettez.
Pour générer des informations d'identification pour l'authentification de base, vous pouvez utiliser la classe OkHttps Credentials avec sa méthode de base (String, String). La méthode prend un nom d'utilisateur et un mot de passe et renvoie les informations d'authentification pour le schéma Http Basic.
Credentials.basic("ausername","apassword");
Si vous souhaitez utiliser le jeton d'API et ne pas utiliser le schéma de base, appelez simplement la méthode getUserDetails (String) avec votre jeton.
5.2. Authentification avec les intercepteurs OkHttp.
La mĂ©thode ci-dessus ajoute des informations d'identification uniquement si vous demandez des donnĂ©es utilisateur. Si vous avez plus d'appels qui nĂ©cessitent une authentification, vous pouvez utiliser un intercepteur pour ce faire. Un intercepteur est utilisĂ© pour modifier chaque demande avant son exĂ©cution et dĂ©finit l'en-tĂȘte de la demande. L'avantage est que vous n'avez pas besoin d'ajouter @ Header ("Autorisation") Ă chaque dĂ©finition de mĂ©thode API.
Pour ajouter un intercepteur, vous devez utiliser la méthode okhttp3.OkHttpClient.Builder.addInterceptor (Interceptor) dans OkHttp Builder.
OkHttpClient okHttpClient = new OkHttpClient().newBuilder().addInterceptor(new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder().header("Authorization", Credentials.basic("aUsername", "aPassword")); Request newRequest = builder.build(); return chain.proceed(newRequest); } }).build();
Le client créé par OkHttp doit ĂȘtre ajoutĂ© Ă votre client Retrofit Ă l'aide de la mĂ©thode retrofit2.Retrofit.Builder.client (OkHttpClient).
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .build();
Comme vous l'avez remarqué, la classe Credentials est utilisée ici pour l'autorisation de base.
Encore une fois, si vous souhaitez utiliser le jeton d'API, utilisez simplement le jeton Ă la place.
6. Exercice: Utilisation de Retrofit pour demander Gerrit en Java
La section suivante décrit comment créer une application Java minimale qui utilise Retrofit pour récupérer des objets de modification ouverts à partir de l'API Gerrit. Les résultats sont imprimés dans la console.
6.1. Création et mise en place d'un projet
Cet exercice suppose que vous
maĂźtrisez Gradle et
Buildship for Eclipse .
Créez un nouveau projet Gradle nommé com.vogella.java.retrofitgerrit. Ajoutez un nouveau package à src / main / java nommé com.vogella.java.retrofitgerrit.
Ajoutez les dépendances suivantes au fichier build.gradle.
// retrofit implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' // Junit testImplementation("org.junit.jupiter:junit-jupiter-api:5.0.0") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0") // to run JUnit 3/4 tests: testImplementation("junit:junit:4.12") testRuntime("org.junit.vintage:junit-vintage-engine:4.12.0")
6.2. Définir l'API et l'adaptateur de modification
Dans la réponse JSON de Gerrit, nous ne nous intéressons qu'à la question des changements. Par conséquent, créez la classe de données suivante dans le package par défaut précédemment ajouté.
package com.vogella.java.retrofitgerrit; public class Change { String subject; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } }
Définissez l'API REST pour Retrofit à l'aide de l'interface suivante.
package com.vogella.java.retrofitgerrit; import java.util.List; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; public interface GerritAPI { @GET("changes/") Call<List<Change>> loadChanges(@Query("q") String status); }
Créez la classe de contrÎleur suivante. Cette classe crée un client Retrofit, appelle l'API Gerrit et traite le résultat (imprime le résultat de l'appel à la console).
package com.vogella.java.retrofitgerrit; import java.util.List; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class Controller implements Callback<List<Change>> { static final String BASE_URL = "https://git.eclipse.org/r/"; public void start() { Gson gson = new GsonBuilder() .setLenient() .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); GerritAPI gerritAPI = retrofit.create(GerritAPI.class); Call<List<Change>> call = gerritAPI.loadChanges("status:open"); call.enqueue(this); } @Override public void onResponse(Call<List<Change>> call, Response<List<Change>> response) { if(response.isSuccessful()) { List<Change> changesList = response.body(); changesList.forEach(change -> System.out.println(change.subject)); } else { System.out.println(response.errorBody()); } } @Override public void onFailure(Call<List<Change>> call, Throwable t) { t.printStackTrace(); } }
Créez une classe avec une méthode principale pour démarrer le contrÎleur.
package com.vogella.java.retrofitgerrit; public class Main { public static void main(String[] args) { Controller controller = new Controller(); controller.start(); } }
7. Exercice: Utilisation de Retrofit pour convertir une réponse XML à partir d'un flux RSS
Cette section décrit comment utiliser Retrofit pour transformer une réponse XML à l'aide de SimpleXMLConverter.
Une application Java minimale est créée qui demande le flux RSS de Vogella (
http://vogella.com/article.rss ) et imprime le nom du canal, le titre et les liens des articles.
7.1. Création et mise en place d'un projet
Cet exercice suppose que vous
maĂźtrisez Gradle et
Buildship for Eclipse .
Créez un nouveau projet Gradle nommé com.vogella.java.retrofitxml. Ajoutez un nouveau package à src / main / java nommé com.vogella.java.retrofitxml.
Ajoutez les dépendances suivantes au fichier build.gradle.
implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'
7.2. Définition d'une vue XML
Un flux RSS est le suivant:
<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>Eclipse and Android Information</title> <link>http://www.vogella.com</link> <description>Eclipse and Android Information</description> <language>en</language> <copyright>Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany (CC BY-NC-SA 3.0)</copyright> <pubDate>Tue, 03 May 2016 11:46:11 +0200</pubDate> <item> <title>Android user interface testing with Espresso - Tutorial</title> <description> This tutorial describes how to test Android applications with the Android Espresso testing framework.</description> <link>http://www.vogella.com/tutorials/AndroidTestingEspresso/article.html</link> <author>lars.vogel@vogella.com (Lars Vogel)</author> <guid>http://www.vogella.com/tutorials/AndroidTestingEspresso/article.html</guid> </item> <item> <title>Using the Gradle build system in the Eclipse IDE - Tutorial</title> <description>This article describes how to use the Gradle tooling in Eclipse</description> <link>http://www.vogella.com/tutorials/EclipseGradle/article.html</link> <author>lars.vogel@vogella.com (Lars Vogel)</author> <guid>http://www.vogella.com/tutorials/EclipseGradle/article.html</guid> </item> <item> <title>Unit tests with Mockito - Tutorial</title> <description>This tutorial explains testing with the Mockito framework for writting software tests.</description> <link>http://www.vogella.com/tutorials/Mockito/article.html</link> <author>lars.vogel@vogella.com (Lars Vogel)</author> <guid>http://www.vogella.com/tutorials/Mockito/article.html</guid> </item> </channel> </rss>
En plus de l'en-tĂȘte XML, ce fichier se compose de divers Ă©lĂ©ments XML. Un Ă©lĂ©ment RSS contient un Ă©lĂ©ment channel qui contient d'autres Ă©lĂ©ments (par exemple, title, description, pubDate) et plusieurs Ă©lĂ©ments item (articles).
Créez les deux classes de données suivantes: RSSFeed et Article.
package com.vogella.java.retrofitxml; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; @Root(name = "item", strict = false) public class Article { @Element(name = "title") private String title; @Element(name = "link") private String link; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } }
package com.vogella.java.retrofitxml; import java.util.List; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Path; import org.simpleframework.xml.Root; @Root(name="rss", strict=false) public class RSSFeed { @Element(name="title") @Path("channel") private String channelTitle; @ElementList(name="item", inline=true) @Path("channel") private List<Article> articleList; public String getChannelTitle() { return channelTitle; } public void setChannelTitle(String channelTitle) { this.channelTitle = channelTitle; } public List<Article> getArticleList() { return articleList; } public void setArticleList(List<Article> articleList) { this.articleList = articleList; } }
La classe Article représente un article et ne conserve que le titre et le lien vers l'article. Ce sont les seuls domaines qui nous intéressent.
Annotation @ Root marque une classe comme faisant l'objet d'une (dĂ©) sĂ©rialisation. Facultativement, vous pouvez spĂ©cifier un nom dans l'annotation @ Root qui correspond au nom de l'Ă©lĂ©ment XML. Si aucun nom n'est spĂ©cifiĂ©, le nom de classe est utilisĂ© comme nom de l'Ă©lĂ©ment XML. Ătant donnĂ© que le nom de classe (RSSFeed) est diffĂ©rent du nom de l'Ă©lĂ©ment XML (rss), nous devons spĂ©cifier le nom.
Lorsque strict est dĂ©fini sur false, l'analyse stricte est dĂ©sactivĂ©e. Cela indique Ă l'analyseur de ne pas interrompre ou lever une exception si un Ă©lĂ©ment ou attribut XML est trouvĂ© pour lequel aucun mappage n'est fourni. Ătant donnĂ© que l'Ă©lĂ©ment rss a un attribut de version pour lequel il n'y a pas de champ correspondant, l'application gĂ©nĂ©rera une erreur si le paramĂštre strict n'est pas dĂ©fini sur false.
à l'aide de l'annotation @ Element, un élément XML est représenté. Si nécessaire, vous pouvez spécifier le nom XML de l'élément représenté par ce champ. Si aucun nom n'est spécifié, le nom du champ est utilisé.
Le champ articleList est annotĂ© avec @ ElementList. Cela montre que ce champ est utilisĂ© pour stocker une collection (dans notre cas: Liste) d'Ă©lĂ©ments XML du mĂȘme nom. Lorsque inline est dĂ©fini sur true, cela signifie que les Ă©lĂ©ments de la collection sont rĂ©pertoriĂ©s l'un aprĂšs l'autre immĂ©diatement Ă l'intĂ©rieur de l'Ă©lĂ©ment spĂ©cifiĂ© et n'ont pas de parent intermĂ©diaire.
à l'aide de l'annotation @ Path, vous pouvez spécifier le chemin d'accÚs à un élément XML dans l'arborescence XML. Ceci est utile si vous ne souhaitez pas modéliser une arborescence XML complÚte avec des objets Java. Pour le nom du canal et plusieurs éléments d'élément, nous pouvons pointer directement vers des éléments spécifiques dans l'élément de canal.
7.3. Définition d'une API et d'un adaptateur de mise à niveau
Définissez l'API REST pour Retrofit via l'interface suivante.
package com.vogella.java.retrofitxml; import retrofit2.Call; import retrofit2.http.GET; public interface VogellaAPI { @GET("article.rss") Call<RSSFeed> loadRSSFeed(); }
Créez la classe de contrÎleur suivante. Cette classe crée un client Retrofit, appelle l'API Vogella et traite le résultat.
package com.vogella.java.retrofitxml; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.simplexml.SimpleXmlConverterFactory; public class Controller implements Callback<RSSFeed> { static final String BASE_URL = "http://vogella.com/"; public void start() { Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL) .addConverterFactory(SimpleXmlConverterFactory.create()).build(); VogellaAPI vogellaAPI = retrofit.create(VogellaAPI.class); Call<RSSFeed> call = vogellaAPI.loadRSSFeed(); call.enqueue(this); } @Override public void onResponse(Call<RSSFeed> call, Response<RSSFeed> response) { if (response.isSuccessful()) { RSSFeed rss = response.body(); System.out.println("Channel title: " + rss.getChannelTitle()); rss.getArticleList().forEach( article -> System.out.println("Title: " + article.getTitle() + " Link: " + article.getLink())); } else { System.out.println(response.errorBody()); } } @Override public void onFailure(Call<RSSFeed> call, Throwable t) { t.printStackTrace(); } }
La derniÚre étape consiste à créer une classe avec une méthode principale pour démarrer le contrÎleur.
package com.vogella.java.retrofitxml; public class Application { public static void main(String[] args) { Controller ctrl = new Controller(); ctrl.start(); } }
8. Exercice: création d'une application pour demander StackOverflow
StackOverflow est un site populaire pour les problÚmes liés à la programmation. Il fournit également une API REST, bien documentée sur
la page de l'API Stackoverflow .
Dans cet exercice, vous allez utiliser la bibliothÚque REST Retrofit. Vous l'utiliserez pour interroger les questions de balises StackOverflow et leurs réponses.
Dans notre exemple, nous utilisons l'URL de demande suivante. Ouvrez cette URL dans un navigateur et regardez la réponse.
https://api.stackexchange.com/2.2/search?order=desc&sort=votes&tagged=android&site=stackoverflow
8.1. Création et mise en place d'un projet
Créez une application Android appelée com.vogella.android.stackoverflow. Utilisez com.vogella.android.stackoverflow comme nom d'un package de niveau supérieur.
Ajoutez les dépendances suivantes au fichier build.gradle.
compile "com.android.support:recyclerview-v7:25.3.1" compile 'com.google.code.gson:gson:2.8.1'
8.2. Création d'un modÚle de données
Nous sommes intéressés par les questions et réponses de Stackoverflow. Pour cela, créez les deux classes de données suivantes.
package com.vogella.android.stackoverflow; import com.google.gson.annotations.SerializedName; public class Question { public String title; public String body; @SerializedName("question_id") public String questionId; @Override public String toString() { return(title); } }
package com.vogella.android.stackoverflow; import com.google.gson.annotations.SerializedName; public class Answer { @SerializedName("answer_id") public int answerId; @SerializedName("is_accepted") public boolean accepted; public int score; @Override public String toString() { return answerId + " - Score: " + score + " - Accepted: " + (accepted ? "Yes" : "No"); } }
8.3. Création d'activité et de mise en page
Définissez activity_main.xml pour votre activité.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dp" android:orientation="vertical" tools:context="com.vogella.android.stackoverflow.MainActivity"> <Spinner android:id="@+id/questions_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v7.widget.RecyclerView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <Button android:id="@+id/authenticate_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="onClick" android:text="Authenticate" /> </LinearLayout>
Ajoutez la classe de vue du recycleur d'adaptateurs nommée RecyclerViewAdapter à votre projet.
Une mise en Ćuvre possible est la suivante.
package com.vogella.android.stackoverflow; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.List; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private List<Answer> data; public class ViewHolder extends RecyclerView.ViewHolder { public TextView text; public ViewHolder(View v) { super(v); text = (TextView) v.findViewById(android.R.id.text1); } } public RecyclerViewAdapter(List<Answer> data) { this.data = data; } @Override public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v; v = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_selectable_list_item, parent, false); return new ViewHolder(v); } @Override public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, int position) { Answer answer = ((Answer) data.get(position)); holder.text.setText(answer.toString()); holder.itemView.setTag(answer.answerId); } @Override public int getItemCount() { return data.size(); } }
Modifiez la classe MainActivity comme suit:
package com.vogella.android.stackoverflow; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends Activity implements View.OnClickListener { private String token; private Button authenticateButton; private Spinner questionsSpinner; private RecyclerView recyclerView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); questionsSpinner = (Spinner) findViewById(R.id.questions_spinner); questionsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "Spinner item selected", Toast.LENGTH_LONG).show(); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); authenticateButton = (Button) findViewById(R.id.authenticate_button); recyclerView = (RecyclerView) findViewById(R.id.list); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); } @Override protected void onResume() { super.onResume(); if (token != null) { authenticateButton.setEnabled(false); } } @Override public void onClick(View v) { switch (v.getId()) { case android.R.id.text1: if (token != null) {
8.4. Utiliser un faux fournisseur de données
Créez un faux fournisseur de données et remplissez le spinner avec de fausses questions et recyclageview avec de fausses réponses (aprÚs avoir modifié la sélection dans le spinner).
package com.vogella.android.stackoverflow; import java.util.ArrayList; import java.util.List; public class FakeDataProvider { public static List<Question> getQuestions(){ List<Question> questions = new ArrayList<>(); for (int i = 0; i<10; i++) { Question question = new Question(); question.questionId = String.valueOf(i); question.title = String.valueOf(i); question.body = String.valueOf(i) + "Body"; questions.add(question); } return questions; } public static List<Answer> getAnswers(){ List<Answer> answers = new ArrayList<>(); for (int i = 0; i<10; i++) { Answer answer = new Answer(); answer.answerId = i; answer.accepted = false; answer.score = i; answers.add(answer); } return answers; } }
Configurez maintenant spinner et recyclerview pour utiliser ces fausses données.
package com.vogella.android.stackoverflow; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; import android.widget.Toast; import java.util.List; public class MainActivity extends Activity implements View.OnClickListener { private String token; private Button authenticateButton; private Spinner questionsSpinner; private RecyclerView recyclerView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); questionsSpinner = (Spinner) findViewById(R.id.questions_spinner); questionsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "Spinner item selected", Toast.LENGTH_LONG).show(); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); List<Question> questions = FakeDataProvider.getQuestions(); ArrayAdapter<Question> arrayAdapter = new ArrayAdapter<Question>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, questions); questionsSpinner.setAdapter(arrayAdapter); authenticateButton = (Button) findViewById(R.id.authenticate_button); recyclerView = (RecyclerView) findViewById(R.id.list); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); List<Answer> answers = FakeDataProvider.getAnswers(); RecyclerViewAdapter adapter = new RecyclerViewAdapter(answers); recyclerView.setAdapter(adapter); } @Override protected void onResume() { super.onResume(); if (token != null) { authenticateButton.setEnabled(false); } } @Override public void onClick(View v) { switch (v.getId()) { case android.R.id.text1: if (token != null) {
8.5. Ajout de dépendances et d'autorisations Gradle
Ajoutez les dépendances suivantes au fichier build.gradle. implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
Ajoutez l'autorisation d'accéder à Internet dans le manifeste. <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.vogella.android.stackoverflow"> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
8.6. Définition d'une API et d'un adaptateur de mise à niveau
L'API Stackoverflow encapsule les réponses ou les questions dans un objet JSON nommé items. Pour gérer cela, créez la classe de données suivante appelée ListWrapper. Cela est nécessaire pour traiter l'encapsuleur des éléments Stackoverflow. Cette classe prend un paramÚtre de type et contient simplement une liste d'objets de ce type. package com.vogella.android.stackoverflow; import java.util.List; public class ListWrapper<T> { List<T> items; }
Définissez l'API REST pour Retrofit via l'interface suivante. package com.vogella.android.stackoverflow; import java.util.List; import okhttp3.ResponseBody; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Path; import retrofit2.Call; public interface StackOverflowAPI { String BASE_URL = "https://api.stackexchange.com/"; @GET("/2.2/questions?order=desc&sort=votes&site=stackoverflow&tagged=android&filter=withbody") Call<ListWrapper<Question>> getQuestions(); @GET("/2.2/questions/{id}/answers?order=desc&sort=votes&site=stackoverflow") Call<ListWrapper<Answer>> getAnswersForQuestion(@Path("id") String questionId); }
8.7. Cadre d'activité
Modifiez le code MainActivity comme suit. package com.vogella.android.stackoverflow; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; import android.widget.Toast; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.ArrayList; import java.util.List; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends Activity implements View.OnClickListener { private StackOverflowAPI stackoverflowAPI; private String token; private Button authenticateButton; private Spinner questionsSpinner; private RecyclerView recyclerView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); questionsSpinner = (Spinner) findViewById(R.id.questions_spinner); questionsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Question question = (Question) parent.getAdapter().getItem(position); stackoverflowAPI.getAnswersForQuestion(question.questionId).enqueue(answersCallback); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); authenticateButton = (Button) findViewById(R.id.authenticate_button); recyclerView = (RecyclerView) findViewById(R.id.list); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); createStackoverflowAPI(); stackoverflowAPI.getQuestions().enqueue(questionsCallback); } @Override protected void onResume() { super.onResume(); if (token != null) { authenticateButton.setEnabled(false); } } private void createStackoverflowAPI() { Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(StackOverflowAPI.BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); stackoverflowAPI = retrofit.create(StackOverflowAPI.class); } @Override public void onClick(View v) { switch (v.getId()) { case android.R.id.text1: if (token != null) {
8.8. Facultatif: obtenir une image de profil utilisateur
Modifiez la disposition des lignes dans la vue du recycleur pour afficher Ă©galement l'image du profil utilisateur. Ătendez votre modĂšle de donnĂ©es pour obtenir une photo de profil de l'utilisateur qui a rĂ©pondu Ă la question. Ajoutez une ImageView aux lignes de mise en page et utilisez la bibliothĂšque Glide pour charger l'image.8.9. Facultatif: utilisez diffĂ©rentes dispositions pour les lignes paires et impaires
Modifiez l'implémentation de l'adaptateur pour utiliser différentes dispositions pour les lignes paires et impaires.Cela nécessite la création de différentes dispositions en fonction du type de données. Utilisez getItemViewType () dans l'adaptateur.8.10. Facultatif: gestion des erreurs réseau
Si vous rencontrez une défaillance du réseau, affichez le bouton de nouvelle demande au lieu de l'interface utilisateur principale.9. Exercice: utiliser Retrofit pour accéder à l'API GitHub sur Android
Cet exercice dĂ©crit comment rĂ©pertorier tous les rĂ©fĂ©rentiels GitHub pour un utilisateur dans une application Android Ă l'aide de Retrofit. Vous pouvez sĂ©lectionner le rĂ©fĂ©rentiel dans la liste dĂ©roulante et spĂ©cifier les discussions relatives Ă l'utilisateur du rĂ©fĂ©rentiel sĂ©lectionnĂ©.Ensuite, vous pouvez sĂ©lectionner une discussion dans le champ dĂ©roulant supplĂ©mentaire et publier un commentaire Ă ce sujet. DialogFragment sera utilisĂ© pour entrer les informations d'identification pour l'authentification.Assurez-vous d'avoir un compte Github, car cela est nĂ©cessaire pour cet exercice. Ătant donnĂ© que Retrofit sera utilisĂ© avec RxJava2 pendant cet exercice, faites Ă©galement attention au didacticiel RxJava2 .9.1. Configuration du projet
Créez une application Android appelée Retrofit Github. Utilisez com.vogella.android.retrofitgithub comme nom du package de niveau supérieur et utilisez un modÚle vide. Assurez-vous que le drapeau «Compatibilité descendante» est coché.Pour utiliser Retrofit et RxJava2 CallAdapter , ajoutez les dépendances suivantes au fichier build.gradle implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:converter-gson:2.3.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
Ajoutez l'autorisation d'accéder à Internet dans le manifeste. <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.vogella.android.retrofitgithub"> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.vogella.android.retrofitgithub.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
9.2. Définition de l'API
Créez les deux classes de données suivantes: GithubIssue et GithubRepo. package com.vogella.android.retrofitgithub; import com.google.gson.annotations.SerializedName; public class GithubIssue { String id; String title; String comments_url; @SerializedName("body") String comment; @Override public String toString() { return id + " - " + title; } }
package com.vogella.android.retrofitgithub; public class GithubRepo { String name; String owner; String url; @Override public String toString() { return(name + " " + url); } }
Ă partir des informations du rĂ©fĂ©rentiel, seuls le nom et l'URL du rĂ©fĂ©rentiel seront affichĂ©s dans la liste dĂ©roulante. Nous ajoutons Ă©galement le propriĂ©taire Ă la classe de donnĂ©es, car le nom du propriĂ©taire est nĂ©cessaire pour demander des discussions plus tard.Nous affichons uniquement l'identifiant et le titre de la discussion dans le champ dĂ©roulant, nous crĂ©ons donc un champ pour chacun d'eux. De plus, la rĂ©ponse de Github contient une URL pour publier un commentaire, qui est stockĂ©e dans le champ comments_url. Pour publier un nouveau commentaire sur l'API Github ultĂ©rieurement, ajoutez un champ appelĂ© commentaire. Github apiindique que le contenu du commentaire doit ĂȘtre liĂ© Ă un champ nommĂ© body dans la demande JSON. Ătant donnĂ© que Retrofit (de) sĂ©rialise tous les champs en fonction de leur nom et que nous ne voulons pas utiliser le corps comme nom de champ dans notre classe GithubIssue, nous utilisons l'annotation @SerializedName. Avec cette annotation, nous pouvons changer le nom avec lequel le champ est (dĂ©) sĂ©rialisĂ© en JSON.Malheureusement, la classe GithubRepo n'est pas suffisante pour demander toutes les informations nĂ©cessaires sur le rĂ©fĂ©rentiel. Comme vous voyez ici, le propriĂ©taire du rĂ©fĂ©rentiel est un objet JSON distinct dans la rĂ©ponse du rĂ©fĂ©rentiel, et a donc gĂ©nĂ©ralement besoin de la classe Java appropriĂ©e pour (dĂ©) sĂ©rialiser. Heureusement, Retrofit vous permet d'ajouter votre propre JSONDeserializer typĂ© pour contrĂŽler la dĂ©sĂ©rialisation d'un type spĂ©cifique. Chaque fois qu'un objet d'un certain type doit ĂȘtre dĂ©sĂ©rialisĂ©, ce dĂ©sĂ©rialiseur personnalisĂ© est utilisĂ©.Pour ce faire, ajoutez la classe GithubRepoDeserialzer suivante. package com.vogella.android.retrofitgithub; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import java.lang.reflect.Type; public class GithubRepoDeserializer implements JsonDeserializer<GithubRepo> { @Override public GithubRepo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { GithubRepo githubRepo = new GithubRepo(); JsonObject repoJsonObject = json.getAsJsonObject(); githubRepo.name = repoJsonObject.get("name").getAsString(); githubRepo.url = repoJsonObject.get("url").getAsString(); JsonElement ownerJsonElement = repoJsonObject.get("owner"); JsonObject ownerJsonObject = ownerJsonElement.getAsJsonObject(); githubRepo.owner = ownerJsonObject.get("login").getAsString(); return githubRepo; } }
Définissez l'API REST pour Retrofit via l'interface suivante: package com.vogella.android.retrofitgithub; import java.util.List; import io.reactivex.Single; import okhttp3.ResponseBody; import retrofit2.http.Body; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Path; import retrofit2.http.Url; public interface GithubAPI { String ENDPOINT = "https://api.github.com"; @GET("user/repos?per_page=100") Single<List<GithubRepo>> getRepos(); @GET("/repos/{owner}/{repo}/issues") Single<List<GithubIssue>> getIssues(@Path("owner") String owner, @Path("repo") String repository); @POST Single<ResponseBody> postComment(@Url String url, @Body GithubIssue issue); }
Vous pouvez avoir une question sur l'annotation @ Url. En utilisant cette annotation, nous pouvons spĂ©cifier l'URL de cette demande. Cela nous permet de modifier dynamiquement l'URL de chaque demande. Nous en avons besoin pour le champ comments_url de la classe GithubIssue.@ Les annotations de chemin lient la valeur du paramĂštre Ă la variable correspondante (accolades) dans l'URL de la demande. Cela est nĂ©cessaire pour indiquer le propriĂ©taire et le nom du rĂ©fĂ©rentiel pour lequel des discussions doivent ĂȘtre demandĂ©es.9.3. BoĂźte de dialogue CrĂ©er des informations d'identification
Pour donner à l'utilisateur la possibilité de stocker ses informations d'identification dans l'application, DialogFragment est utilisé. Créez donc la classe suivante appelée CredentialsDialog et ajoutez également un fichier de disposition appelé dialog_credentials.xml au dossier de disposition des ressources.Le résultat devrait ressembler à la capture d'écran suivante.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginTop="16dp" android:text="Fill you credentials here" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.3" android:text="Username:" /> <EditText android:id="@+id/username_edittext" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.7" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.3" android:text="Password" /> <EditText android:id="@+id/password_edittext" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.7" android:inputType="textPassword" /> </LinearLayout> </LinearLayout>
package com.vogella.android.retrofitgithub; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.view.View; import android.widget.EditText; import okhttp3.Credentials; public class CredentialsDialog extends DialogFragment { EditText usernameEditText; EditText passwordEditText; ICredentialsDialogListener listener; public interface ICredentialsDialogListener { void onDialogPositiveClick(String username, String password); } @Override public void onAttach(Context context) { super.onAttach(context); if (getActivity() instanceof ICredentialsDialogListener) { listener = (ICredentialsDialogListener) getActivity(); } } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_credentials, null); usernameEditText = (EditText) view.findViewById(R.id.username_edittext); passwordEditText = (EditText) view.findViewById(R.id.password_edittext); usernameEditText.setText(getArguments().getString("username")); passwordEditText.setText(getArguments().getString("password")); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) .setView(view) .setTitle("Credentials") .setNegativeButton("Cancel", null) .setPositiveButton("Continue", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (listener != null) { listener.onDialogPositiveClick(usernameEditText.getText().toString(), passwordEditText.getText().toString()); } } }); return builder.create(); } }
9.4. Créer une activité
Modifiez activity_main.xml comme suit. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.vogella.android.retrofitgithub.MainActivity"> <android.support.v7.widget.Toolbar android:id="@+id/my_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <Spinner android:id="@+id/repositories_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Spinner android:id="@+id/issues_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/repositories_spinner" /> <EditText android:id="@+id/comment_edittext" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/issues_spinner" android:enabled="false" android:hint="Your comment" android:imeOptions="actionDone" android:inputType="text" android:maxLines="1" /> <Button android:id="@+id/loadRepos_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:enabled="false" android:gravity="center" android:onClick="onClick" android:text="Load user repositories" /> <Button android:id="@+id/send_comment_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@id/loadRepos_button" android:enabled="false" android:onClick="onClick" android:text="Send comment" /> </RelativeLayout> </LinearLayout>
Deux boutons (pour télécharger les référentiels et envoyer des commentaires), deux Spinner (champ déroulant pour afficher les référentiels et les discussions) et EditText (pour écrire des commentaires). Pour lancer CredentialsDialog, utilisez le menu de la barre d'outils Android. Pour le créer, ajoutez un fichier xml de menu nommé menu_main.xml au dossier des ressources de menu (créez un dossier s'il n'existe pas). <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_credentials" android:title="Credentials"/> </menu>
Puisque nous utilisons le widget Toolbar, vous devez désactiver la barre d'actions par défaut. Pour ce faire, modifiez le fichier de style xml comme indiqué ci-dessous. <resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> </resources>
Modifiez votre code d'activité comme suit. package com.vogella.android.retrofitgithub; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.IOException; import java.util.List; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.observers.DisposableSingleObserver; import io.reactivex.schedulers.Schedulers; import okhttp3.Credentials; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends AppCompatActivity implements CredentialsDialog.ICredentialsDialogListener { GithubAPI githubAPI; String username; String password; Spinner repositoriesSpinner; Spinner issuesSpinner; EditText commentEditText; Button sendButton; Button loadReposButtons; private CompositeDisposable compositeDisposable = new CompositeDisposable(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.my_toolbar); setSupportActionBar(toolbar); sendButton = (Button) findViewById(R.id.send_comment_button); repositoriesSpinner = (Spinner) findViewById(R.id.repositories_spinner); repositoriesSpinner.setEnabled(false); repositoriesSpinner.setAdapter(new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, new String[]{"No repositories available"})); repositoriesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (parent.getSelectedItem() instanceof GithubRepo) { GithubRepo githubRepo = (GithubRepo) parent.getSelectedItem(); compositeDisposable.add(githubAPI.getIssues(githubRepo.owner, githubRepo.name) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(getIssuesObserver())); } } @Override public void onNothingSelected(AdapterView<?> parent) { } }); issuesSpinner = (Spinner) findViewById(R.id.issues_spinner); issuesSpinner.setEnabled(false); issuesSpinner.setAdapter(new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, new String[]{"Please select repository"})); commentEditText = (EditText) findViewById(R.id.comment_edittext); loadReposButtons = (Button) findViewById(R.id.loadRepos_button); createGithubAPI(); } @Override protected void onStop() { super.onStop(); if (compositeDisposable != null && !compositeDisposable.isDisposed()) { compositeDisposable.dispose(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_credentials: showCredentialsDialog(); return true; } return super.onOptionsItemSelected(item); } private void showCredentialsDialog() { CredentialsDialog dialog = new CredentialsDialog(); Bundle arguments = new Bundle(); arguments.putString("username", username); arguments.putString("password", password); dialog.setArguments(arguments); dialog.show(getSupportFragmentManager(), "credentialsDialog"); } private void createGithubAPI() { Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .registerTypeAdapter(GithubRepo.class, new GithubRepoDeserializer()) .create(); OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder().header("Authorization", Credentials.basic(username, password)); Request newRequest = builder.build(); return chain.proceed(newRequest); } }).build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(GithubAPI.ENDPOINT) .client(okHttpClient) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); githubAPI = retrofit.create(GithubAPI.class); } public void onClick(View view) { switch (view.getId()) { case R.id.loadRepos_button: compositeDisposable.add(githubAPI.getRepos() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(getRepositoriesObserver())); break; case R.id.send_comment_button: String newComment = commentEditText.getText().toString(); if (!newComment.isEmpty()) { GithubIssue selectedItem = (GithubIssue) issuesSpinner.getSelectedItem(); selectedItem.comment = newComment; compositeDisposable.add(githubAPI.postComment(selectedItem.comments_url, selectedItem) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(getCommentObserver())); } else { Toast.makeText(MainActivity.this, "Please enter a comment", Toast.LENGTH_LONG).show(); } break; } } private DisposableSingleObserver<List<GithubRepo>> getRepositoriesObserver() { return new DisposableSingleObserver<List<GithubRepo>>() { @Override public void onSuccess(List<GithubRepo> value) { if (!value.isEmpty()) { ArrayAdapter<GithubRepo> spinnerAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, value); repositoriesSpinner.setAdapter(spinnerAdapter); repositoriesSpinner.setEnabled(true); } else { ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, new String[]{"User has no repositories"}); repositoriesSpinner.setAdapter(spinnerAdapter); repositoriesSpinner.setEnabled(false); } } @Override public void onError(Throwable e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "Can not load repositories", Toast.LENGTH_SHORT).show(); } }; } private DisposableSingleObserver<List<GithubIssue>> getIssuesObserver() { return new DisposableSingleObserver<List<GithubIssue>>() { @Override public void onSuccess(List<GithubIssue> value) { if (!value.isEmpty()) { ArrayAdapter<GithubIssue> spinnerAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, value); issuesSpinner.setEnabled(true); commentEditText.setEnabled(true); sendButton.setEnabled(true); issuesSpinner.setAdapter(spinnerAdapter); } else { ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, new String[]{"Repository has no issues"}); issuesSpinner.setEnabled(false); commentEditText.setEnabled(false); sendButton.setEnabled(false); issuesSpinner.setAdapter(spinnerAdapter); } } @Override public void onError(Throwable e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "Can not load issues", Toast.LENGTH_SHORT).show(); } }; } private DisposableSingleObserver<ResponseBody> getCommentObserver() { return new DisposableSingleObserver<ResponseBody>() { @Override public void onSuccess(ResponseBody value) { commentEditText.setText(""); Toast.makeText(MainActivity.this, "Comment created", Toast.LENGTH_LONG).show(); } @Override public void onError(Throwable e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "Can not create comment", Toast.LENGTH_SHORT).show(); } }; } @Override public void onDialogPositiveClick(String username, String password) { this.username = username; this.password = password; loadReposButtons.setEnabled(true); } }
Ici, nous avons ajouté le GithubRepoDeserializer précédemment créé en tant que TypeAdapter dans GsonBuilder. Pour gérer l'authentification pour chaque appel, un intercepteur a été ajouté à OkHttpClient. Afin que les méthodes API retournent les types RxJava2, nous avons ajouté le RxJava2 CallAdapter à notre client.10. Exercice: Utilisation de Retrofit avec OAuth pour demander des informations utilisateur à Twitter sur Android
Cet exercice décrit comment accéder à Twitter à l'aide de Retrofit sur Android. Nous allons écrire une application qui peut demander et afficher les données utilisateur pour le nom d'utilisateur fourni. Dans cet exercice, nous utilisons l' authentification Twitter uniquement avec OAuth 2 pour l'autorisation. Pour faire cet exercice, vous devez avoir un compte Twitter. De plus, vous devez aller sur les applications Twitter et créer une nouvelle application pour obtenir votre clé client et votre secret client. Nous en aurons besoin plus tard pour demander notre jeton OAuth.10.1. Configuration du projet
Créez une application Android appelée Retrofit Twitter. Utilisez com.vogella.android.retrofittwitter comme nom d'un package de niveau supérieur.Pour utiliser Retrofit, ajoutez les lignes suivantes au fichier build.gradle implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
Ajoutez l'autorisation d'accéder à Internet dans le manifeste. <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.vogella.android.retrofittwitter"> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
10.2. Définition de l'API
Créez les deux classes de données suivantes, appelées OAuthToken et UserDetails. package com.vogella.android.retrofittwitter; import com.google.gson.annotations.SerializedName; public class OAuthToken { @SerializedName("access_token") private String accessToken; @SerializedName("token_type") private String tokenType; public String getAccessToken() { return accessToken; } public String getTokenType() { return tokenType; } public String getAuthorization() { return getTokenType() + " " + getAccessToken(); } }
package com.vogella.android.retrofittwitter; public class UserDetails { private String name; private String location; private String description; private String url; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
La classe OAuthToken est utilisée pour stocker le jeton porteur que nous demandons à Twitter, avec notre clé et notre secret. Nous utilisons l'annotation @ SerializedName pour définir le nom Retrofit sur (dé) sérialiser les champs.La classe UserDetails enregistre simplement plusieurs champs de la réponse Twitter lors de la demande d'informations utilisateur. Nous ne montrons pas toutes les données utilisateur contenues dans la réponse, seulement le nom, l'emplacement, l'URL et la description.Définissez l'API REST pour Retrofit via l'interface suivante: package com.vogella.android.retrofittwitter; import retrofit2.Call; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Query; public interface TwitterApi { String BASE_URL = "https://api.twitter.com/"; @FormUrlEncoded @POST("oauth2/token") Call<OAuthToken> postCredentials(@Field("grant_type") String grantType); @GET("/1.1/users/show.json") Call<UserDetails> getUserDetails(@Query("screen_name") String name); }
10.3. Créer une activité
Modifiez le fichier activity_main.xml et la classe MainActivity correspondante comme suit: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.vogella.android.retrofittwitter.MainActivity"> <LinearLayout android:id="@+id/username_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:orientation="horizontal"> <TextView android:id="@+id/username_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:enabled="false" android:gravity="center_vertical" android:text="Username:" /> <EditText android:id="@+id/username_edittext" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:enabled="false" android:gravity="center" android:imeOptions="actionDone" android:inputType="text" android:maxLines="1" /> </LinearLayout> <Button android:id="@+id/request_token_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:onClick="onClick" android:text="Request token" /> <Button android:id="@+id/request_user_details_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@id/request_token_button" android:enabled="false" android:onClick="onClick" android:text="Request user details" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/request_user_details_button" android:layout_below="@id/username_container" android:gravity="center" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="Name:" /> <TextView android:id="@+id/name_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="---" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="Location:" /> <TextView android:id="@+id/location_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="---" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="Url:" /> <TextView android:id="@+id/url_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="---" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="Description:" /> <TextView android:id="@+id/description_textview" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:text="---" /> </LinearLayout> </LinearLayout> </RelativeLayout>
package com.vogella.android.retrofittwitter; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import okhttp3.Credentials; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends AppCompatActivity { private String credentials = Credentials.basic("aConsumerKey", "aSecret"); Button requestTokenButton; Button requestUserDetailsButton; EditText usernameEditText; TextView usernameTextView; TextView nameTextView; TextView locationTextView; TextView urlTextView; TextView descriptionTextView; TwitterApi twitterApi; OAuthToken token; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestTokenButton = (Button) findViewById(R.id.request_token_button); requestUserDetailsButton = (Button) findViewById(R.id.request_user_details_button); usernameEditText = (EditText) findViewById(R.id.username_edittext); usernameTextView = (TextView) findViewById(R.id.username_textview); nameTextView = (TextView) findViewById(R.id.name_textview); locationTextView = (TextView) findViewById(R.id.location_textview); urlTextView = (TextView) findViewById(R.id.url_textview); descriptionTextView = (TextView) findViewById(R.id.description_textview); createTwitterApi(); } private void createTwitterApi() { OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder().header("Authorization", token != null ? token.getAuthorization() : credentials); Request newRequest = builder.build(); return chain.proceed(newRequest); } }).build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(TwitterApi.BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build(); twitterApi = retrofit.create(TwitterApi.class); } public void onClick(View view) { switch (view.getId()) { case R.id.request_token_button: twitterApi.postCredentials("client_credentials").enqueue(tokenCallback); break; case R.id.request_user_details_button: String editTextInput = usernameEditText.getText().toString(); if (!editTextInput.isEmpty()) { twitterApi.getUserDetails(editTextInput).enqueue(userDetailsCallback); } else { Toast.makeText(this, "Please provide a username", Toast.LENGTH_LONG).show(); } break; } } Callback<OAuthToken> tokenCallback = new Callback<OAuthToken>() { @Override public void onResponse(Call<OAuthToken> call, Response<OAuthToken> response) { if (response.isSuccessful()) { requestTokenButton.setEnabled(false); requestUserDetailsButton.setEnabled(true); usernameTextView.setEnabled(true); usernameEditText.setEnabled(true); token = response.body(); } else { Toast.makeText(MainActivity.this, "Failure while requesting token", Toast.LENGTH_LONG).show(); Log.d("RequestTokenCallback", "Code: " + response.code() + "Message: " + response.message()); } } @Override public void onFailure(Call<OAuthToken> call, Throwable t) { t.printStackTrace(); } }; Callback<UserDetails> userDetailsCallback = new Callback<UserDetails>() { @Override public void onResponse(Call<UserDetails> call, Response<UserDetails> response) { if (response.isSuccessful()) { UserDetails userDetails = response.body(); nameTextView.setText(userDetails.getName() == null ? "no value" : userDetails.getName()); locationTextView.setText(userDetails.getLocation() == null ? "no value" : userDetails.getLocation()); urlTextView.setText(userDetails.getUrl() == null ? "no value" : userDetails.getUrl()); descriptionTextView.setText(userDetails.getDescription().isEmpty() ? "no value" : userDetails.getDescription()); } else { Toast.makeText(MainActivity.this, "Failure while requesting user details", Toast.LENGTH_LONG).show(); Log.d("UserDetailsCallback", "Code: " + response.code() + "Message: " + response.message()); } } @Override public void onFailure(Call<UserDetails> call, Throwable t) { t.printStackTrace(); } }; }
Remplacez aConsumerKey et aSecret par la clĂ© client et le secret reçus de Twitter.Jetez Ă©galement un Ćil Ă l'intercepteur que nous ajoutons Ă notre client Retrofit. Parce que nous utilisons OAuth, nos informations d'identification sont diffĂ©rentes pour chaque appel. La mĂ©thode postCredentials doit publier les informations d'identification (clĂ© du consommateur et secret) dans le schĂ©ma Twitter de base. Par consĂ©quent, cet appel renvoie un jeton de support, que Retrofit dĂ©sĂ©rialise dans notre classe OAuthToken, qui est ensuite stockĂ© dans le champ de jeton. Toute autre demande peut (et devrait) maintenant utiliser ce jeton comme informations d'identification pour l'autorisation. Des informations sur l'utilisateur sont Ă©galement demandĂ©es.11. Ressources de rĂ©novation
Tutoriel sur la consommation d'API avec Retrofit
Dans une série de blogs de département sur Retrofit
Consommation d'API avec Retrofit