Retrofit 2.xをRESTクライアントとして使用する-チュートリアル

1.後付け



1.1。 レトロフィットとは?



Retrofitは、JavaおよびAndroid用のRESTクライアントです。 RESTベースのWebサービスを介してJSON(またはその他の構造化データ)を簡単に取得およびロードできます。 Retrofitでは、データのシリアル化に使用するコンバーターを構成します。 通常、GSonはJSONに使用されますが、独自のコンバーターを追加してXMLまたは他のプロトコルを処理できます。 Retrofitは、HTTP要求にOkHttpライブラリを使用します。



次のツールを使用してJSONベースのJavaオブジェクトを作成できます。www.jsonschema2pojo.orgこれは、既存のJSONから複雑なJavaデータ構造を作成するのに役立ちます。





1.2。 レトロフィットの使用



Retrofitを使用するには、次の3つのクラスが必要です。





各インターフェイスメソッドは、可能なAPI呼び出しの1つを表します。 要求のタイプと相対URLを示すHTTPアノテーション(GET、POSTなど)が必要です。 戻り値は、予想される結果タイプでCallオブジェクトの応答を完了します。



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





置換ブロックとクエリパラメーターを使用して、URLを構成できます。 置換ブロックは、{}を使用して相対URLに追加されます。 メソッドパラメーターに@ Pathアノテーションを使用すると、このパラメーターの値は特定の置換ブロックにバインドされます。



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





クエリパラメータは、@ Queryアノテーションを使用してメソッドパラメータに追加されます。 URLの最後に自動的に追加されます。



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





メソッドパラメータへの@ Bodyアノテーションは、Retrofitにオブジェクトを呼び出すリクエストボディとして使用するように指示します。



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





2.前提条件



次の例では、Eclipse IDEとGradleビルドシステムを使用しています。

この演習では、 Gradleに精通しており、EclipseGradle使用していることを前提としています。



Visual Studio CodeやIntelliJなどの他の開発環境でも同じことができるため、お気に入りのツールを使用できます。



3.演習:最初のレトロフィットクライアント



この演習では、スタンドアロンRESTクライアントを作成します。 応答はMockサーバーによって生成されます。



3.1。 プロジェクトの作成と設定



com.vogella.retrofitgerritという名前の新しいGradleプロジェクトを作成します。 com.vogella.retrofitgerritという名前の新しいパッケージをsrc / main / javaに追加します。



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。 APIおよびレトロフィットアダプターの定義



GerritのJSONレスポンスでは、変更の問題にのみ関心があります。 したがって、以前に追加したデフォルトパッケージに次のデータクラスを作成します。



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





次のインターフェースを使用して、レトロフィット用のREST APIを定義します。



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





次のコントローラークラスを作成します。 このクラスは、Retrofitクライアントを作成し、Gerrit APIを呼び出し、結果を処理します(呼び出し結果をコンソールに出力します)。



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





コントローラーを開始するためのmainメソッドを持つクラスを作成します。



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





4.レトロフィットコンバーターとアダプター



4.1。 レトロフィットコンバーター



レトロフィットは、特定のコンバーターを使用するように構成できます。 このコンバーターは、データの(デ)シリアル化を処理します。 いくつかのコンバーターは、さまざまなシリアル化形式で既に利用可能です。





リストされたコンバーターに加えて、Converter.Factoryクラスを拡張することにより、独自のコンバーターを作成して他のプロトコルを処理することもできます。



4.2。 レトロフィットアダプター



アダプタを使用してRetrofitを拡張し、RxJava 2.x、Java 8、Guavaなどの他のライブラリと対話することもできます。



利用可能なアダプターの概要は、Github square / retrofit / retrofit-adapters /にあります。



たとえば、RxJava 2.xアダプターはGradleを使用して接続できます。



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





またはApache Mavenを使用:



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





アダプターを追加するには、retrofit2.Retrofit.Builder.addCallAdapterFactory(Factory)メソッドを使用する必要があります。



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





このアダプターを使用すると、RetrofitインターフェースはRxJava 2.xタイプ(Observable、Flowable、Singleなど)を返すことができます。



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





5.レトロフィット認証



Retrofitは、認証を必要とするAPI呼び出しをサポートしています。 認証は、ユーザー名とパスワード(Http基本認証)またはAPIトークンを使用して実行できます。



認証を管理するには2つの方法があります。 最初の方法は、注釈を使用して要求ヘッダーを管理することです。 別の方法は、これにOkHttpインターセプターを使用することです。



5.1。 アノテーションを使用した認証



認証が必要なユーザーに関する情報を要求するとします。 これを行うには、API定義に新しいパラメーターを追加します。次に例を示します。



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







@ヘッダー(「Authorization」)注釈を使用して、Retrofitに、渡した値とともにAuthorizationヘッダーをリクエストに追加するように指示します。



基本認証の資格情報を生成するには、OkHttps Credentialsクラスとそのベース(文字列、文字列)メソッドを使用できます。 このメソッドは、ユーザー名とパスワードを受け取り、Http Basicスキームの認証資格情報を返します。



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





APIトークンを使用し、Basicスキームを使用しない場合は、トークンでgetUserDetails(String)メソッドを呼び出すだけです。



5.2。 OkHttpインターセプターによる認証。



上記の方法は、ユーザーデータを要求する場合にのみ資格情報を追加します。 認証を必要とする呼び出しがさらにある場合は、インターセプターを使用してこれを行うことができます。 インターセプターは、実行前に各要求を変更し、要求ヘッダーを設定するために使用されます。 利点は、各APIメソッド定義に@ヘッダー(「承認」)を追加する必要がないことです。



インターセプターを追加するには、OkHttp Builderでokhttp3.OkHttpClient.Builder.addInterceptor(Interceptor)メソッドを使用する必要があります。



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





OkHttpによって作成されたクライアントは、retrofit2.Retrofit.Builder.client(OkHttpClient)メソッドを使用してRetrofitクライアントに追加する必要があります。



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





お気づきのように、ここではCredentialsクラスが基本認証に使用されます。

繰り返しますが、APIトークンを使用する場合は、代わりにトークンを使用してください。



6.演習:Retrofitを使用してJavaでGerritを要求する



次のセクションでは、Retrofitを使用してGerrit APIからオープンな変更オブジェクトを取得する最小限のJavaアプリケーションを作成する方法について説明します。 結果はコンソールに出力されます。



6.1。 プロジェクトの作成と設定



この演習では、 Gradle and Buildship for Eclipseに精通していることを前提としています。



com.vogella.java.retrofitgerritという名前の新しいGradleプロジェクトを作成します。 com.vogella.java.retrofitgerritという名前の新しいパッケージをsrc / main / javaに追加します。



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。 APIおよびレトロフィットアダプターの定義



GerritのJSONレスポンスでは、変更の問題にのみ関心があります。 したがって、以前に追加したデフォルトパッケージに次のデータクラスを作成します。



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





次のインターフェースを使用して、レトロフィット用のREST APIを定義します。



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





次のコントローラークラスを作成します。 このクラスは、Retrofitクライアントを作成し、Gerrit APIを呼び出し、結果を処理します(呼び出し結果をコンソールに出力します)。



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





コントローラーを開始するためのmainメソッドを持つクラスを作成します。



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





7.演習:Retrofitを使用してRSSフィードからXMLレスポンスを変換する



このセクションでは、SimpleXMLConverterを使用してRetrofitを使用してXML応答を変換する方法について説明します。



Vogella RSSフィード( http://vogella.com/article.rss )を要求し、チャンネル名、タイトル、記事のリンクを印刷する最小限のJavaアプリケーションが作成されます。



7.1。 プロジェクトの作成と設定



この演習では、 Gradle and Buildship for Eclipseに精通していることを前提としています。



com.vogella.java.retrofitxmlという名前の新しいGradleプロジェクトを作成します。 com.vogella.java.retrofitxmlという名前の新しいパッケージをsrc / main / javaに追加します。



build.gradleファイルに次の依存関係を追加します。



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





7.2。 XMLビューの定義



RSSフィードは次のとおりです。



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





XMLヘッダーに加えて、このファイルはさまざまなXML要素で構成されています。 RSS要素には、他の要素(たとえば、タイトル、説明、pubDate)およびいくつかのアイテム要素(記事)を含むチャネル要素が含まれます。



次の2つのデータクラスを作成します:RSSFeedと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; } }
      
      





Articleクラスは1つの記事を表し、記事へのタイトルとリンクのみを保持します。 これらは私たちが興味を持っている唯一のフィールドです。



アノテーション@ Rootは、クラスを(デ)シリアル化の対象としてマークします。 オプションで、XML要素の名前と一致する名前を@ Rootアノテーションに指定できます。 名前が指定されていない場合、クラス名はXML要素の名前として使用されます。 クラス名(RSSFeed)はXML要素名(rss)とは異なるため、名前を指定する必要があります。



strictをfalseに設定すると、厳密な解析は無効になります。 これにより、マッピングが提供されていないXML要素または属性が見つかった場合に、パーサーに割り込みも例外もスローしないように指示します。 rss要素には対応するフィールドがないversion属性があるため、strictパラメーターがfalseに設定されていない場合、アプリケーションはエラーを生成します。



@ Elementアノテーションを使用して、XML要素が表されます。 必要に応じて、このフィールドで表される要素のXML名を指定できます。 名前が指定されていない場合、フィールド名が使用されます。



articleListフィールドには、@ ElementListの注釈が付けられています。 これは、このフィールドを使用して、同じ名前のXML要素のコレクション(この場合はリスト)を格納することを示しています。 inlineがtrueに設定されている場合、これはコレクション内のアイテムが指定されたアイテムのすぐ内側に次々にリストされ、中間の親がないことを意味します。



@ Pathアノテーションを使用して、XMLツリー内のXML要素へのパスを指定できます。 これは、Javaオブジェクトで完全なXMLツリーをモデル化したくない場合に便利です。 チャンネルの名前といくつかのアイテム要素については、チャンネル要素内の特定の要素を直接指すことができます。



7.3。 APIとレトロフィットアダプターの定義



次のインターフェースを使用して、レトロフィット用のREST APIを定義します。



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





次のコントローラークラスを作成します。 このクラスは、レトロフィットクライアントを作成し、Vogella APIを呼び出し、結果を処理します。



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





最後のステップは、コントローラーを開始するためのメインメソッドを持つクラスを作成することです。



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





8.演習:StackOverflowを要求するアプリケーションの作成



StackOverflowは、プログラミング関連の問題で人気のあるサイトです。 また、Stackoverflow APIページで詳しく説明されいるREST APIも提供します



この演習では、REST Retrofitライブラリを使用します。 これを使用して、StackOverflowタグの質問とその回答を照会します。



この例では、次のリクエストURLを使用します。 このURLをブラウザーで開き、答えを見てください。



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





8.1。 プロジェクトの作成と設定



com.vogella.android.stackoverflowというAndroidアプリを作成します。 最上位パッケージの名前としてcom.vogella.android.stackoverflowを使用します。



build.gradleファイルに次の依存関係を追加します。



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





8.2。 データモデルの作成



Stackoverflowからの質問と回答に興味があります。 この目的のために、次の2つのデータクラスを作成します。



 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。 アクティビティとレイアウトの作成



アクティビティの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: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>
      
      





RecyclerViewAdapterという名前のアダプターリサイクラビュークラスをプロジェクトに追加します。



1つの可能な実装は次のとおりです。



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





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 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。 偽のデータプロバイダーを使用する



偽のデータプロバイダーを作成し、スピナーに偽の質問を入力し、recyclerviewに偽の回答を入力します(スピナーで選択を変更した後)。



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





次に、この偽のデータを使用するようにスピナーとリサイクラビューを構成します。



 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



この演習では、Retrofitを使用して、AndroidアプリケーションでユーザーのすべてのGitHubリポジトリを一覧表示する方法について説明します。ドロップダウンリストからリポジトリを選択し、選択したリポジトリのユーザーに関連するディスカッションを指定できます。



次に、追加のドロップダウンフィールドからディスカッションを選択して、コメントを投稿できます。DialogFragmentは、認証用の資格情報を入力するために使用されます。



Githubアカウントを持っていることを確認してください。これはこの演習に必要です。この演習ではRxJava2でRetrofitが使用されるため、RxJava2チュートリアルにも注意してください



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のみがドロップダウンリストに表示されます。後でディスカッションをリクエストするには所有者の名前が必要なので、データクラスに所有者も追加します。



ドロップダウンフィールドにはディスカッションの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を動的に変更できます。これはGithubIssueクラスのcomments_urlフィールドに必要です。



@ Pathアノテーションは、パラメータ値をリクエストURLの対応する変数(中括弧)にバインドします。これは、ディスカッションを要求するリポジトリの所有者と名前を示すために必要です。



9.3。資格情報の作成ダイアログボックス



ユーザーが資格情報をアプリケーションに保存できるようにするために、DialogFragmentが使用されます。したがって、CredentialsDialogという次のクラスを作成し、dialog_credentials.xmlというレイアウトファイルをリソースレイアウトフォルダーに追加します。



結果は、次のスクリーンショットのようになります。







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







2つのボタン(リポジトリのロードとコメントの送信用)、2つのSpinner(リポジトリとディスカッションを表示するためのドロップダウンフィールド)およびEditText(コメントの作成用)。CredentialsDialogを起動するには、Androidツールバーのメニューを使用します。作成するには、menu_main.xmlという名前の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クラスは、キーとシークレットと共に、Twitterから要求するベアラートークンを格納するために使用されます。@ SerializedNameアノテーションを使用して、Retrofitの名前をフィールドのシリアル化(デ)に設定します。



UserDetailsクラスは、ユーザー情報を要求するときにTwitter応答からいくつかのフィールドを保存するだけです。応答に含まれていたすべてのユーザーデータは表示されず、名前、場所、URL、説明のみが表示されます。



次のインターフェースを使用して、レトロフィット用のREST APIを定義します。



 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_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を、Twitterから受け取ったコンシューマキーとシークレットに置き換えます。



また、Retrofitクライアントに追加するインターセプターも見てください。OAuthを使用しているため、資格情報は呼び出しごとに異なります。postCredentialsメソッドは、基本的なTwitterスキーマに資格情報(コンシューマキーとシークレット)を投稿する必要があります。その結果、この呼び出しはベアラトークンを返し、RetrofitはこれをOAuthTokenクラスに逆シリアル化し、トークンフィールドに格納します。他のすべてのリクエストは、このトークンを承認の資格情報として使用できます(使用する必要があります)。ユーザー情報も要求されます。



11.レトロフィットリソース



Retrofitを使用したAPIの使用チュートリアル



Retrofitについてのdeptブログシリーズ



Introfitを使用したAPIの使用




All Articles