Verwenden von Retrofit 2.x als REST-Client - Lernprogramm

1. Nachrüstung


1.1. Was ist Nachrüstung?


Retrofit ist ein REST-Client für Java und Android. Es macht es einfach, JSON (oder andere strukturierte Daten) über einen REST-basierten Webdienst abzurufen und zu laden. In Retrofit konfigurieren Sie, welcher Konverter zum Serialisieren von Daten verwendet wird. Normalerweise wird GSon für JSON verwendet, aber Sie können Ihre eigenen Konverter hinzufügen, um XML oder andere Protokolle zu verarbeiten. Retrofit verwendet die OkHttp-Bibliothek für HTTP-Anforderungen.

Sie können JSON-basierte Java-Objekte mit dem folgenden Tool erstellen : www.jsonschema2pojo.org Dies kann nützlich sein, um komplexe Java-Datenstrukturen aus vorhandenem JSON zu erstellen.


1.2. Nachrüstung verwenden


Um mit Retrofit arbeiten zu können, benötigen Sie die folgenden drei Klassen:

  • Modellklasse, die als JSON-Modell verwendet wird
  • Schnittstellen, die mögliche HTTP-Vorgänge identifizieren
  • Die Retrofit.Builder-Klasse ist eine Instanz, die die Builder-Schnittstelle und die API verwendet, um eine URL-Endpunktdefinition für HTTP-Vorgänge zu definieren.

Jede Schnittstellenmethode repräsentiert einen der möglichen API-Aufrufe. Es muss eine HTTP-Annotation (GET, POST usw.) enthalten, um den Anfragetyp und die relative URL anzugeben. Der Rückgabewert vervollständigt die Antwort im Call-Objekt mit dem erwarteten Ergebnistyp.

@GET("users") Call<List<User>> getUsers(); 

Sie können die Ersatzblöcke und Abfrageparameter verwenden, um die URL zu konfigurieren. Der Ersetzungsblock wird mit {} an die relative URL angehängt. Bei Verwendung der Annotation @ Path für einen Methodenparameter wird der Wert dieses Parameters an einen bestimmten Ersetzungsblock gebunden.

 @GET("users/{name}/commits") Call<List<Commit>> getCommitsByName(@Path("name") String name); 

Abfrageparameter werden mithilfe der Annotation @ Query zum Methodenparameter hinzugefügt. Sie werden automatisch am Ende der URL hinzugefügt.

 @GET("users") Call<User> getUserById(@Query("id") Integer id); 

Die Annotation @ Body zum Methodenparameter weist Retrofit an, das Objekt als aufrufenden Anforderungshauptteil zu verwenden.

 @POST("users") Call<User> postUser(@Body User user) 

2. Voraussetzungen


In den folgenden Beispielen wird die Eclipse-IDE mit dem Gradle-Buildsystem verwendet.
In dieser Übung wird davon ausgegangen, dass Sie mit Gradle und Gradle mit Eclipse vertraut sind.

Andere Entwicklungsumgebungen wie Visual Studio Code oder IntelliJ machen dasselbe, sodass Sie Ihr Lieblingswerkzeug verwenden können.

3. Übung: Erster Retrofit-Client


In dieser Übung erstellen Sie einen eigenständigen REST-Client. Antworten werden vom Mock-Server generiert.

3.1. Projekt erstellen und einrichten


Erstellen Sie ein neues Gradle-Projekt mit dem Namen com.vogella.retrofitgerrit. Fügen Sie src / main / java ein neues Paket mit dem Namen com.vogella.retrofitgerrit hinzu.

Fügen Sie der Datei build.gradle die folgenden Abhängigkeiten hinzu.

 // 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. API und Nachrüstadapter definieren


In Gerrits JSON-Antwort interessiert uns nur die Frage der Änderungen. Erstellen Sie daher die folgende Datenklasse im zuvor hinzugefügten Standardpaket.

 package com.vogella.java.retrofitgerrit; public class Change { String subject; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } } 

Definieren Sie die REST-API für die Nachrüstung über die folgende Schnittstelle.

 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); } 

Erstellen Sie die folgende Controller-Klasse. Diese Klasse erstellt einen Retrofit-Client, ruft die Gerrit-API auf und verarbeitet das Ergebnis (druckt das Ergebnis des Aufrufs an die Konsole).

 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(); } } 

Erstellen Sie eine Klasse mit einer Hauptmethode, um den Controller zu starten.

 package com.vogella.java.retrofitgerrit; public class Main { public static void main(String[] args) { Controller controller = new Controller(); controller.start(); } } 

4. Wandler und Adapter nachrüsten


4.1. Nachrüstkonverter


Die Nachrüstung kann für die Verwendung eines bestimmten Konverters konfiguriert werden. Dieser Konverter übernimmt die (De-) Serialisierung von Daten. Für verschiedene Serialisierungsformate stehen bereits mehrere Konverter zur Verfügung.

  • So konvertieren Sie nach JSON und umgekehrt:
    • Gson: com.squareup.retrofit: converter-gson
    • Jackson: com.squareup.retrofit: Konverter-Jackson
    • Moshi: com.squareup.retrofit: Konverter-Moshi

  • So konvertieren Sie in Protokollpuffer und umgekehrt:
    • Protobuf: com.squareup.retrofit: converter-protobuf
    • Draht: com.squareup.retrofit: Konverterdraht

  • So konvertieren Sie nach XML und umgekehrt:
    • Einfaches XML: com.squareup.retrofit: converter-simplexml


Zusätzlich zu den aufgelisteten Konvertern können Sie auch eigene erstellen, um andere Protokolle zu verarbeiten, indem Sie die Converter.Factory-Klasse erweitern.

4.2. Nachrüstadapter


Retrofit kann auch mit Adaptern für die Schnittstelle mit anderen Bibliotheken wie RxJava 2.x, Java 8 und Guava erweitert werden.

Eine Übersicht der verfügbaren Adapter finden Sie unter Github square / retrofit / retrofit-adapters / .

Beispielsweise kann der RxJava 2.x-Adapter mit Gradle verbunden werden:

 compile 'com.squareup.retrofit2:adapter-rxjava2:latest.version' 

oder mit Apache Maven:

 <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>adapter-rxjava2</artifactId> <version>latest.version</version> </dependency> 

Um einen Adapter hinzuzufügen, müssen Sie die Methode retrofit2.Retrofit.Builder.addCallAdapterFactory (Factory) verwenden.

 Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); 

Mit diesem Adapter können Retrofit-Schnittstellen RxJava 2.x-Typen zurückgeben, z. B. Observable, Flowable oder Single usw.

 @GET("users") Observable<List<User>> getUsers(); 

5. Authentifizierung nachrüsten


Retrofit unterstützt API-Aufrufe, die eine Authentifizierung erfordern. Die Authentifizierung kann mit einem Benutzernamen und einem Kennwort (HTTP-Basisauthentifizierung) oder einem API-Token erfolgen.

Es gibt zwei Möglichkeiten, die Authentifizierung zu verwalten. Die erste Methode besteht darin, den Anforderungsheader mithilfe von Anmerkungen zu verwalten. Eine andere Möglichkeit besteht darin, dafür einen OkHttp-Interceptor zu verwenden.

5.1. Authentifizierung mit Anmerkungen


Angenommen, Sie möchten Informationen zu einem Benutzer anfordern, für den eine Authentifizierung erforderlich ist. Sie können dies tun, indem Sie der API-Definition einen neuen Parameter hinzufügen, zum Beispiel:

 @GET("user") Call<UserDetails> getUserDetails(@Header("Authorization") String credentials) 


Mit der Annotation @ Header ("Authorization") weisen Sie Retrofit an, den Authorization-Header mit dem von Ihnen übergebenen Wert zur Anforderung hinzuzufügen.

Um Anmeldeinformationen für die Standardauthentifizierung zu generieren, können Sie die OkHttps Credentials-Klasse mit ihrer Basismethode (String, String) verwenden. Die Methode verwendet einen Benutzernamen und ein Kennwort und gibt Authentifizierungsdaten für das HTTP-Basisschema zurück.

 Credentials.basic("ausername","apassword"); 

Wenn Sie das API-Token und nicht das Basisschema verwenden möchten, rufen Sie einfach die Methode getUserDetails (String) mit Ihrem Token auf.

5.2. Authentifizierung mit OkHttp-Interceptors.


Mit der obigen Methode werden Anmeldeinformationen nur hinzugefügt, wenn Sie Benutzerdaten anfordern. Wenn Sie mehr Anrufe haben, für die eine Authentifizierung erforderlich ist, können Sie dazu einen Interceptor verwenden. Ein Interceptor wird verwendet, um jede Anforderung zu ändern, bevor sie ausgeführt wird, und um den Anforderungsheader festzulegen. Der Vorteil ist, dass Sie nicht jeder API-Methodendefinition @ Header ("Authorization") hinzufügen müssen.

Um einen Interceptor hinzuzufügen, müssen Sie die okhttp3.OkHttpClient.Builder.addInterceptor (Interceptor) -Methode in OkHttp Builder verwenden.

 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(); 

Der von OkHttp erstellte Client muss mit der Methode retrofit2.Retrofit.Builder.client (OkHttpClient) zu Ihrem Retrofit-Client hinzugefügt werden.

 Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .build(); 

Wie Sie bemerkt haben, wird hier die Klasse Anmeldeinformationen für die Basisautorisierung verwendet.
Wenn Sie das API-Token verwenden möchten, verwenden Sie stattdessen einfach das Token.

6. Übung: Verwenden von Retrofit zum Anfordern von Gerrit in Java


Im folgenden Abschnitt wird beschrieben, wie Sie eine minimale Java-Anwendung erstellen, die Retrofit verwendet, um offene Änderungsobjekte von der Gerrit-API abzurufen. Die Ergebnisse werden in der Konsole gedruckt.

6.1. Projekt erstellen und einrichten


In dieser Übung wird davon ausgegangen, dass Sie mit Gradle und Buildship for Eclipse vertraut sind.

Erstellen Sie ein neues Gradle-Projekt mit dem Namen com.vogella.java.retrofitgerrit. Fügen Sie src / main / java ein neues Paket mit dem Namen com.vogella.java.retrofitgerrit hinzu.

Fügen Sie der Datei build.gradle die folgenden Abhängigkeiten hinzu.

 // 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. API und Nachrüstadapter definieren


In Gerrits JSON-Antwort interessiert uns nur die Frage der Änderungen. Erstellen Sie daher die folgende Datenklasse im zuvor hinzugefügten Standardpaket.

 package com.vogella.java.retrofitgerrit; public class Change { String subject; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } } 

Definieren Sie die REST-API für die Nachrüstung über die folgende Schnittstelle.

 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); } 

Erstellen Sie die folgende Controller-Klasse. Diese Klasse erstellt einen Retrofit-Client, ruft die Gerrit-API auf und verarbeitet das Ergebnis (druckt das Ergebnis des Aufrufs an die Konsole).

 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(); } } 

Erstellen Sie eine Klasse mit einer Hauptmethode, um den Controller zu starten.

 package com.vogella.java.retrofitgerrit; public class Main { public static void main(String[] args) { Controller controller = new Controller(); controller.start(); } } 

7. Übung: Verwenden von Retrofit zum Konvertieren einer XML-Antwort aus einem RSS-Feed


In diesem Abschnitt wird beschrieben, wie Sie mit Retrofit eine XML-Antwort mit SimpleXMLConverter transformieren.

Es wird eine minimale Java-Anwendung erstellt, die den Vogella-RSS-Feed ( http://vogella.com/article.rss ) anfordert und den Kanalnamen, den Titel und die Artikellinks druckt.

7.1. Projekt erstellen und einrichten


In dieser Übung wird davon ausgegangen, dass Sie mit Gradle und Buildship for Eclipse vertraut sind.

Erstellen Sie ein neues Gradle-Projekt mit dem Namen com.vogella.java.retrofitxml. Fügen Sie src / main / java ein neues Paket mit dem Namen com.vogella.java.retrofitxml hinzu.

Fügen Sie der Datei build.gradle die folgenden Abhängigkeiten hinzu.

 implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0' 

7.2. Definieren einer XML-Ansicht


Ein RSS-Feed lautet wie folgt:

 <?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> 

Neben dem XML-Header besteht diese Datei aus verschiedenen XML-Elementen. Ein RSS-Element enthält ein Kanalelement, das andere Elemente (z. B. Titel, Beschreibung, pubDate) und mehrere Elementelemente (Artikel) enthält.

Erstellen Sie die folgenden zwei Datenklassen: RSSFeed und 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; } } 

Die Artikelklasse stellt einen Artikel dar und behält nur den Titel und den Link zum Artikel bei. Dies sind die einzigen Bereiche, die uns interessieren.

Annotation @ Root markiert eine Klasse als Gegenstand einer (De-) Serialisierung. Optional können Sie in der Annotation @ Root einen Namen angeben, der mit dem Namen des XML-Elements übereinstimmt. Wenn kein Name angegeben wird, wird der Klassenname als Name des XML-Elements verwendet. Da sich der Klassenname (RSSFeed) vom XML-Elementnamen (rss) unterscheidet, müssen wir den Namen angeben.

Wenn strict auf false gesetzt ist, ist das strikte Parsen deaktiviert. Dies weist den Parser an, keine Ausnahme zu unterbrechen oder auszulösen, wenn ein XML-Element oder -Attribut gefunden wird, für das keine Zuordnung bereitgestellt wird. Da das rss-Element ein Versionsattribut hat, für das es kein entsprechendes Feld gibt, generiert die Anwendung einen Fehler, wenn der strenge Parameter nicht auf false gesetzt ist.

Mit der Annotation @ Element wird ein XML-Element dargestellt. Bei Bedarf können Sie den XML-Namen des durch dieses Feld dargestellten Elements angeben. Wenn kein Name angegeben ist, wird der Feldname verwendet.

Das Feld articleList ist mit @ ElementList versehen. Dies zeigt, dass in diesem Feld eine Sammlung (in unserem Fall: Liste) von XML-Elementen mit demselben Namen gespeichert wird. Wenn inline auf true gesetzt ist, bedeutet dies, dass die Elemente in der Sammlung unmittelbar innerhalb des angegebenen Elements nacheinander aufgelistet werden und kein übergeordnetes Zwischenelement haben.

Mit der Annotation @ Path können Sie den Pfad zu einem XML-Element im XML-Baum angeben. Dies ist nützlich, wenn Sie keinen vollständigen XML-Baum mit Java-Objekten modellieren möchten. Für den Namen des Kanals und mehrerer Elementelemente können wir direkt auf bestimmte Elemente im Kanalelement verweisen.

7.3. Definieren einer API und eines Nachrüstadapters


Definieren Sie die REST-API für die Nachrüstung über die folgende Schnittstelle.

 package com.vogella.java.retrofitxml; import retrofit2.Call; import retrofit2.http.GET; public interface VogellaAPI { @GET("article.rss") Call<RSSFeed> loadRSSFeed(); } 

Erstellen Sie die folgende Controller-Klasse. Diese Klasse erstellt einen Retrofit-Client, ruft die Vogella-API auf und verarbeitet das Ergebnis.

 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(); } } 

Der letzte Schritt besteht darin, eine Klasse mit einer Hauptmethode zum Starten des Controllers zu erstellen.

 package com.vogella.java.retrofitxml; public class Application { public static void main(String[] args) { Controller ctrl = new Controller(); ctrl.start(); } } 

8. Übung: Erstellen einer Anwendung zum Anfordern von StackOverflow


StackOverflow ist eine beliebte Website für programmierbezogene Probleme. Es bietet auch eine REST-API, die auf der Seite Stackoverflow-API gut dokumentiert ist.

In dieser Übung verwenden Sie die REST Retrofit-Bibliothek. Sie werden es verwenden, um Fragen zu StackOverflow-Tags und deren Antworten abzufragen.

In unserem Beispiel verwenden wir die folgende Anforderungs-URL. Öffnen Sie diese URL in einem Browser und sehen Sie sich die Antwort an.

 https://api.stackexchange.com/2.2/search?order=desc&sort=votes&tagged=android&site=stackoverflow 

8.1. Projekt erstellen und einrichten


Erstellen Sie eine Android-App namens com.vogella.android.stackoverflow. Verwenden Sie com.vogella.android.stackoverflow als Namen für ein Paket der obersten Ebene.

Fügen Sie der Datei build.gradle die folgenden Abhängigkeiten hinzu.

 compile "com.android.support:recyclerview-v7:25.3.1" compile 'com.google.code.gson:gson:2.8.1' 

8.2. Erstellen eines Datenmodells


Wir sind an Fragen und Antworten von Stackoverflow interessiert. Erstellen Sie zu diesem Zweck die folgenden zwei Datenklassen.

 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. Aktivität und Layout erstellen


Legen Sie activity_main.xml für Ihre Aktivität fest.

 <?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> 

Fügen Sie Ihrem Projekt die Adapter-Recycler-Ansichtsklasse RecyclerViewAdapter hinzu.

Eine mögliche Implementierung ist wie folgt.

 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(); } } 

Ändern Sie die MainActivity-Klasse wie folgt:

 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) { // TODO } else { Toast.makeText(this, "You need to authenticate first", Toast.LENGTH_LONG).show(); } break; case R.id.authenticate_button: // TODO break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == 1) { token = data.getStringExtra("token"); } } } 

8.4. Verwendung eines gefälschten Datenanbieters


Erstellen Sie einen gefälschten Datenprovider und füllen Sie den Spinner mit gefälschten Fragen und recyceln Sie die Ansicht mit gefälschten Antworten (nachdem Sie die Auswahl im Spinner geändert haben).

 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; } } 

Konfigurieren Sie nun Spinner und Recyclerview so, dass diese gefälschten Daten verwendet werden.

 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) { // TODO } else { Toast.makeText(this, "You need to authenticate first", Toast.LENGTH_LONG).show(); } break; case R.id.authenticate_button: // TODO break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == 1) { token = data.getStringExtra("token"); } } } 

8.5. Gradle


build.gradle .

 implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' 

.

 <?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. API Retrofit


Stackoverflow API JSON items. , ListWrapper. , Stackoverflow. .

 package com.vogella.android.stackoverflow; import java.util.List; public class ListWrapper<T> { List<T> items; } 

REST API Retrofit .

 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. activity


MainActivity .

 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) { //TODO } else { Toast.makeText(this, "You need to authenticate first", Toast.LENGTH_LONG).show(); } break; case R.id.authenticate_button: // TODO break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == 1) { token = data.getStringExtra("token"); } } Callback<ListWrapper<Question>> questionsCallback = new Callback<ListWrapper<Question>>() { @Override public void onResponse(Call<ListWrapper<Question>> call, Response<ListWrapper<Question>> response) { if (response.isSuccessful()) { ListWrapper<Question> questions = response.body(); ArrayAdapter<Question> arrayAdapter = new ArrayAdapter<Question>(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, questions.items); questionsSpinner.setAdapter(arrayAdapter); } else { Log.d("QuestionsCallback", "Code: " + response.code() + " Message: " + response.message()); } } @Override public void onFailure(Call<ListWrapper<Question>> call, Throwable t) { t.printStackTrace(); } }; Callback<ListWrapper<Answer>> answersCallback = new Callback<ListWrapper<Answer>>() { @Override public void onResponse(Call<ListWrapper<Answer>> call, Response<ListWrapper<Answer>> response) { if (response.isSuccessful()) { List<Answer> data = new ArrayList<>(); data.addAll(response.body().items); recyclerView.setAdapter(new RecyclerViewAdapter(data)); } else { Log.d("QuestionsCallback", "Code: " + response.code() + " Message: " + response.message()); } } @Override public void onFailure(Call<ListWrapper<Answer>> call, Throwable t) { t.printStackTrace(); } }; Callback<ResponseBody> upvoteCallback = new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { if (response.isSuccessful()) { Toast.makeText(MainActivity.this, "Upvote successful", Toast.LENGTH_LONG).show(); } else { Log.d("QuestionsCallback", "Code: " + response.code() + " Message: " + response.message()); Toast.makeText(MainActivity.this, "You already upvoted this answer", Toast.LENGTH_LONG).show(); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { t.printStackTrace(); } }; } 

8.8. :


layout recycler view, . , , . ImageView layout Glide .

8.9. : layout'


, .

layout . getItemViewType() .

8.10. :


, .

9. : Retrofit GitHub API Android


, GitHub Android Retrofit. (issues), .

. DialogFragment .

, Github, . Retrofit RxJava2 , RxJava2 Tutorial .

9.1.


Android Retrofit Github. com.vogella.android.retrofitgithub . , «Backwards Compatibility» ( ).

Retrofit RxJava2 CallAdapter , 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' 

.

 <?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. API


: GithubIssue 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); } } 

URL- . (owner) , , .

id , . , Github URL- , comments_url. Github API, comment. Github API , body JSON. Retrofit () , GithubIssue, @SerializedName. , () JSON.

, GithubRepo , . , JSON , Java- (). , Retrofit JSONDeserializer . , , .

GithubRepoDeserialzer.

 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; } } 

REST API Retrofit :

 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); } 

@ Url. URL . URL . comments_url GithubIssue.

@ Path ( ) URL- . , .

9.3. « »


, DialogFragment. CredentialsDialog, layout dialog_credentials.xml layout .

, .



 <?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. Activity


activity_main.xml .

 <?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> 


( ), Spinner ( ) EditText ( ). CredentialsDialog Android. , xml menu_main.xml ( , ).

 <?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> 

Toolbar , action bar . xml style , .

 <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> 

activity .

 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); } } 

GithubRepoDeserializer TypeAdapter GsonBuilder. , Interceptor OkHttpClient. API- RxJava2, RxJava2 CallAdapter .

10. : Retrofit OAuth Twitter Android


, Twitter Retrofit Android. , . Twitter application-only OAuth 2 . , Twitter. , Twitter , consumer key onsumer secret. , OAuth.

10.1.


Android Retrofit Twitter. com.vogella.android.retrofittwitter .

Retrofit, build.gradle

 implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' 

.

 <?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. API


, OAuthToken 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; } } 

OAuthToken bearer token, Twitter, . @ SerializedName Retrofit () .

UserDetails Twitter . , , , , URL .

REST API Retrofit :

 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. Activity


activity_main.xml MainActivity :

 <?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(); } }; } 

aConsumerKey aSecret consumer key secret, Twitter.

, Retrofit . OAuth, . postCredentials (consumer key secret) Basic Twitter. bearer token, Retrofit OAuthToken, . ( ) . .

11. Retrofit


Consuming APIs with Retrofit tutorial

In dept blog series about Retrofit

Consuming APIs with Retrofit

Source: https://habr.com/ru/post/de428736/


All Articles