Retrofit - Ki-Kobayashi/Android-Wiki GitHub Wiki
https://square.github.io/retrofit/
.
https://developer.android.com/codelabs/basic-android-kotlin-compose-getting-data-internet?hl=ja#6
.
🛑Retrofit を使用するには、Jsonコンバーターも必要になる(よく使用されるのは Moshi )
(コード)
https://square.github.io/retrofit/
(バージョン)
※下記のダウンロード項目の1行目末尾に記載あり
https://github.com/square/retrofit?tab=readme-ov-file#download
.
🛑 「lib.version.toml」を使用する場合は、下記ページを参考にする
https://github.com/Ki-Kobayashi/Android-Wiki/wiki/%E2%9C%A8%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AEVer%E7%AE%A1%E7%90%86%E3%81%AF%E3%80%8Clib.version.toml%E3%80%8D%E3%81%A7%EF%BC%81
val retrofit_version = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:$retrofit_version")
implementation("com.squareup.retrofit2:converter-moshi:$retrofit_version")
val moshi_version = "1.14.0"
implementation("com.squareup.moshi:moshi:$moshi_version")
implementation("com.squareup.moshi:moshi-kotlin:$moshi_version")
ksp("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version")
// Retrofit2でデバッグ用にログ出力するやつ
// https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor
val okhttp_logging = "4.12.0"
implementation("com.squareup.okhttp3:logging-interceptor:$okhttp_logging")
.
.
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
// https://github.com/google/secrets-gradle-plugin/tree/main
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") 👈追加
id("com.google.devtools.ksp")
}
.
buildscript {
dependencies {
classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1") 👈追加
}
}
plugins {
id("com.android.application") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("com.google.devtools.ksp") version "1.9.22-1.0.17" apply false
}
.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> 👈追加
.
Moduleの分け方は、以下の2種がある
- パッケージ単位でまとめる
- 機能単位でまとめる(以下例はコッチ)
.
import com.example.udmnewsapiclient.BuildConfig
import com.example.udmnewsapiclient.data.api.NewsApiService
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
/**
* Created by K.Kobayashi on 2022/05/19.
*/
@Module
@InstallIn(SingletonComponent::class)
object ApiModule {
/**
* Retrofit2でデバッグ用にログ出力する設定
* https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor
* https://square.github.io/okhttp/features/interceptors/
*
* ◆ログレベル
* logger.levelで渡しているlevelには4種あり、出力の詳細さが変わる
* ★ NONE
* ログを出力しない
* ★ BASIC
* リクエストラインとレスポンスラインのみを出力
* ★ HEADER
* BASICのログに加えてリクエストヘッダとレスポンスヘッダが出力される
* ★ BODY
* HEADERのログに加えて、クエストボディとレスポンスボディが出力される
*/
@Singleton
@Provides
fun provideOkhttpClient(): OkHttpClient {
val logInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
return OkHttpClient.Builder()
.addInterceptor {
// 💡【下記処理について】
// 特にリクエストに変更を加えたいが元のリクエストを変更せずに残したい場合に有用な処理
// リクエストからHTTPのURLを取得
val httpUrl = it.request().url
// 元のリクエストからコピーを作成し、url(httpUrl)メソッドを使用して新しいURLに更新
val requestBuilder = it.request().newBuilder().url(httpUrl)
// 更新されたリクエストを使って処理を継続
it.proceed(requestBuilder.build())
}
.addInterceptor(logInterceptor)
// 以下3つのタイムアウト:定義なし=デフォルト10秒がセットされる(※ほとんどの場合、デフォルトで問題ない=定義不要)
// "接続タイムアウト"の設定:
.connectTimeout(30, TimeUnit.SECONDS)
// 読み取りタイムアウトの設定:個々の結果取得到着時間
.readTimeout(20, TimeUnit.SECONDS)
// データ送信タイムアウト設定:個々の書き込み IO 操作時間
.writeTimeout(25, TimeUnit.SECONDS)
.build()
}
@Singleton
@Provides
fun provideMoshi(): MoshiConverterFactory {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
return MoshiConverterFactory.create(moshi)
}
@Singleton
@Provides
fun provideRetrofit(moshiConverterFactory: MoshiConverterFactory): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(moshiConverterFactory)
.build()
}
@Singleton
@Provides
fun provideNewsApiService(retrofit: Retrofit): NewsApiService =
retrofit.create(NewsApiService::class.java)
}
interface NewsApiService {
@GET("/v2/top-headlines")
suspend fun getTopHeadlines(): Response<ApiResponse>
}
.
どちらも非同期処理としたい時に、使用する戻り値の型。
- Call: Retrofit 2.6 ”より前”
- Response: Retrofit 2.6 ”以降”
※非同期にする必要がないなら、戻り値の型は、Response とはせず、ただの T 。
.
https://qiita.com/SYABU555/items/3b280a8e81d2cc897383
Retrofit2公式サイトに謳われているとおり、簡単にAPIコールするためのライブラリです。 また、バックグラウンドで行なってコールバックを指定することも簡単にできます。
.
APIを表現、定義します。 例えば、こちらのフリーAPIを参考にさせてもらいます https://randomuser.me/api このとき、ここで定義するのは規定URL https://randomuser.me/ 以降の部分になります
interface NewsApiService {
@GET("/v2/top-headlines")
suspend fun getTopHeadlines(
@Query("country")
country: String,
@Query("page")
page: Int,
@Query("apiKey")
apiKey: String = BuildConfig.NEWS_API_KEY,
): Response<NewsResponse>
}
パラメータの渡し方、パスの作成方法など、
細かい使い方にも触れておきます
.
もっともシンプルな形
@GET("users/list")
このように直接Queryパラメータを書くことも可能です
@GET("users/list?sort=desc")
これはGETに限らず、POST、PUTも同様です
.
URLのパス途中に何かを入れたい場合はこのように
{}
で囲んで命名し、@Path
をNameと一緒に定義することで、渡したパラメータがURLパスの中に代入されます
@GET("group/{id}/users")
fun groupList(@Path("id") groupId : Int) : Response<List<User>>
.
URLの最後につく?以降の要素はこのように書きます
@GET("group/{id}/users")
fun groupList(@Path("id") groupId: Int, @Query("sort") sort: String): Response<List<User>>
これを実行すると、以下のようなURLになる
https://randomuser.me/group/3/users?sort=desc
注意:例なので、動くURLではないです
.
あるインスタンスがもつ要素をkey, value変換して
APIで送りたい、というときには、そのインスタンスクラスを引数にするインターフェースを用意し、@Bodyをつけます。
@POST("users/new")
fun createUser(@Body user: User): Response<User>
ここでのUserは、後述する自作Modelクラスであり、
どのKeyがどのインスタンス変数に該当するかというのを記載する必要がありますが、
そうすることで自動的に変換してJsonに変換して送ることができます
.
変数をkey, valueで変換して送りたい場合(Form指定)は
以下のように@FormUrlEncodedをつけ、送りたいデータには@Fieldをつけます。
@FormUrlEncoded
@POST("user/edit")
fun updateUser(@Field("first_name") first: String, @Field("last_name") last: String): Response<User>
また、Multipart との併用はできません
.
Maltipart指定してデータを送りたい場合にも
以下のように@Multipart
をつけ、送りたいデータには@Partあるいは@PartMap
をつけます。
@Multipart
@PUT("user/photo")
fun updateUser(@Part("photo") photo: RequestBody, @Part("description") description: RequestBody): Response<User>
また、FormUrlEncodedとの併用はできません
.
ヘッダの指定もできます
- 静的指定
- 単体指定
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
fun widgetList(): Response<List<Widget>>
.
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
fun getUser(@Path("username") username: String): Response<User>
.
@GET("user")
fun getUser(@Header("Authorization") authorization: String): Response<User>
.
受け取り(あるいは送信)するデータのModelクラスを作成します。
今回は例として、こちらのvalueがランダムになるフリーAPIをお借りします
{
"info": {
"page": 1,
"results": 1,
"seed": "7aed050e936ddbda",
"version": "1.1"
},
"results": [
{
"cell": "0486-036-722",
"dob": "1979-04-08 06:14:29",
"email": "[email protected]",
"gender": "male",
"id": {
"name": "TFN",
"value": "023064228"
},
"location": {
"city": "port macquarie",
"postcode": 1650,
"state": "western australia",
"street": "3769 dogwood ave"
},
"login": {
"md5": "e7f8bb220a82a28ad9375081f022ff2f",
"password": "aaaaaaa",
"salt": "Q6J1Te1h",
"sha1": "e2ec839f88e8fdaddbb2a83877d9afd8342bf26c",
"sha256": "a602c4c64dc6fc1b94d6c98eedbf4383d79cd8894e21cc9f37f9fca3000cea05",
"username": "greenduck262"
},
"name": {
"first": "roger",
"last": "holt",
"title": "mr"
},
"nat": "AU",
"phone": "09-2659-5113",
"picture": {
"large": "https://randomuser.me/api/portraits/men/78.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/78.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/78.jpg"
},
"registered": "2004-12-21 07:19:21"
}
]
}
で、2階層目まで変換したものがこちら
それぞれのkey名が変数名に該当します
data class RandomUserDemo(var info: Info,
var results: List<Result>)
data class Info(var seed: String,
var results: Int,
var page: Int,
var version: String)
data class Result(var gender: String,
var email: String,
var registered: String,
var dob: String,
var phone: String,
var cell: String)
.
Key名と変数名を違うものにしたい、という場合は
@SerializedNameをつけることで変更可能です
data class Result(var gender: String,
var email: String,
var registered: Int,
var dob: Int,
var phone: Int,
var cell: Int,
@SerializedName("id") var idMap: IdMap)
data class IdMap(var name: String,
var value: String)
.
逆に、key,value に変換したくない要素もあると思います。
そんなときはTransientを使いましょう。
JavaとKotlinで書き方が少し違うのでどちらも載せます
public class IdMap {
public long transient id = 0;
}
data class IdMap(@Transient var id: Long,
var name: String,
var value: String)
.
ログやヘッダー、基底URLの定義などを行い、
HttpClientを作成します
val httpBuilder: OkHttpClient.Builder get() {
// create http client
val httpClient = OkHttpClient.Builder()
.addInterceptor(Interceptor { chain ->
val original = chain.request()
//header
val request = original.newBuilder()
.header("Accept", "application/json")
.method(original.method(), original.body())
.build()
return@Interceptor chain.proceed(request)
})
.readTimeout(30, TimeUnit.SECONDS)
// log interceptor
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
httpClient.addInterceptor(loggingInterceptor)
return httpClient
}
.
HttpClientを使い、Retrofitフレームワークを経由して
APIを定義したinterfaceをインスタンス化します
// core for controller
val service: IApiService = create(IApiService::class.java)
lateinit var retrofit: Retrofit
fun <S> create(serviceClass: Class<S>): S {
val gson = GsonBuilder()
.serializeNulls()
.create()
// create retrofit
retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://randomuser.me/") // Put your base URL
.client(httpBuilder.build())
.build()
return retrofit.create(serviceClass)
}
このserviceがinterfaceであり、実際にAPIコールを行う際に利用するものです
.
実行は以下のように行います
.
try {
val response = API.apiDemo().execute()
if (response.isSuccessful()) {
return response.body()
} else {
// failed
}
} catch (e: IOException e) {
e.printStackTrace()
}
.
参考ソースはJavaですが、こんな感じで非同期にします。 ただお勧めとしてはRxJavaを利用すると、スッキリかけます。
API.apiDemo().enqueue(new Callback<RandomUserDemo>() {
@Override
public void onResponse(Call<RandomUserDemo> call, retrofit2.Response<RandomUserDemo> response) {
RandomUserDemo demo = response.body();
}
@Override
public void onFailure(Call<RandomUserDemo> call, Throwable t) {
}
});
.
.
.
.
.
.
.
.
.