RetrofitはRESTに準拠したWeb APIです。
このRetrofitとKotlin Serializationを使って、Webサービスへアクセスする方法を、まとめました。
サンプルはAndroidのコードラボと同じ内容です。私はコードラボが理解し難かったので、不要な部分をそぎ落として、Retrofitに的を絞って説明しています。
※環境:Android Studio Meerkat | 2024.3.1
Kotlin 2.0.0
Kotlin serialization json 1.7.1
Retrofit2 2.11.0
Coil 3.1.0
目次
Retrofitとは
「Retrofitとは」を説明するには、Retrofitの使われる背景が重要です。WebサービスからRetrofitへ至る話になるので、少々長くなります。
WebサービスとWeb API
200X年の中ごろから、インターネット上で様々なサービスを提供するサイト(企業、団体や組織など)が登場してきました。例えば、提供されるサービスとは「天気予報」「Web検索」「地図情報」「翻訳機能」などです。
サービスを提供する仕組みに、Webサーバー(HTTPサーバー)とHTTPプロトコルが使われる事から、「Webサービス」と呼ばれています。
これらのサービスを利用し易いように、サービスを利用する方法の記述されたプログラムライブラリーが、サイト側から提供されます。このライブラリを「Web API」といいます。
Web APIを使えば、自身が開発するアプリケーションへWebサービスを手軽に組み込めます。
これは、「WWW(WWW:World Wide Web)上に存在する膨大なデータや機能を簡単に活用できる」事に繋がります。
とても、有り難い仕組みです。
SOAPとREST
当初は、Webサービスで使われる通信に、SOAP(Simple Object Access Protocol)が使われていました。
SOAPはWeb上に存在するプログラム間でデータを交換するための「プロトコル」です。HTTPプロトコルで受け渡しされるXMLデータに、メッセージ(コマンド、返答、データなど)を埋め込んで、「上位プロトコル」を構成したものです。
データの信頼性やセキュリティを考慮した高機能なプロトコルですが、その半面、とても通信が複雑です。プログラムが稼働しているシステム間で厳密な通信契約が必要だったりします。
しかし、Webサービスの中には、不特定多数に情報や機能を提供するだけで、SOAPのような高機能は必要ないものもあります。むしろ、そのようなサービスの方が主流かも知れません。
そこで、REST(Representational State Transfer)が提唱されました。
RESTは簡素なWebサービスの形態を定義した「設計思想(指標)」です。「シンプルで実装しやすく、汎用性が優れているWebサービスは、このようにあるべき!」といった要件が4つ定義されています。
このRESTに準拠するWebサービスで行われる通信は、純粋なWebサーバー(HTTPサーバー)とHTTPプロトコルで十分な要件を満たせます。ですので、サービスにアクセスするための特別なWeb APIは必要ありません。
RESTに準拠していれば、Web APIはWebクライアント(HTTPクライアント)の機能があれば十分になりました。
このように、RESTに準拠したWeb APIを「REST API(またはRESTful API)」と言います。
Retrofit
RetrofitはREST APIの一つです。ですので、機能を簡単に表現すれば、Webクライアント(HTTPクライアント)になります。
Ktolin Serializationを組み込めば、Webサーバーから取得したJSONデータ(サービスデータ)を、クラスのオブジェクトに変換して、プログラムへ取り込むことが出来ます。
とても、便利です。
環境設定
「app/build.gradle」へ、次の設定を行います。
plugins { ... id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0" // Ktolinのバージョンに合わせる // id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" // id("org.jetbrains.kotlin.plugin.serialization") version "2.1.0" } android { ... } dependencies { ... // Kotlin Serialization for parsing the JSON format implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1") // implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") // implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0") // retrofit for networking the Web Service implementation ("com.squareup.retrofit2:retrofit:2.11.0") implementation ("com.squareup.retrofit2:converter-moshi:2.11.0") implementation ("com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2") }
※Kotlin Serializationの環境設定については「Kotlin serializationでJSONをパース」を参照
Webサービスへアクセス
※サンプルコードは「developer.android.comのコードラボ」と同じ内容です。
火星の地表の写真を提供するサイト(Webサービス)から、写真のリストを取得するサンプルです。
情報の定義
サイトから提供されるサービスの仕様または実際のデータから、取得する情報の定義を行います。
[ { "id": "424905", "img_src": "https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631300503690E01_DXXX.jpg" }, { "id": "424906", "img_src": "https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg" }, ... ]
写真データは二つの要素(idとimg_src)を持ち、同じデータが配列(またはリスト)になっています。
データクラスで表現すると、以下の通りです。
@Serializable data class Photo( val id: Int, @SerialName("img_src") val imgSrc: String // JSONのキーに別名を定義 )
「JSON⇒クラスのオブジェクト変換」にKotlin Serializationを使うので、@Serializableを付けておきます。
※Kotlin Serializationについては「Kotlin serializationでJSONをパース」を参照
アクセス関数の定義
アクセス関数はインターフェースへ列記します。
このインターフェースをRetrofitが読み込んで解析し、抽象関数をHTTPプロトコルを使った通信に展開してくれます。
interface MarsService { @GET("photos") suspend fun getPhotos(): List<Photo> }
サンプルは情報の取得を行うだけなので、GETコマンドのみが定義されています。
GETの引数はデータを一意に指定する値です。サイトのルート直下に”photos”データがあることを意味します。
Web API(Retrofit)の構築
Retrofit.Builderを使って、Web APIを構築します。
Builder#baseUrl( )の引数はWebサーバーを一意に指定するURLです。
object MarsApi { private val retrofit = Retrofit.Builder() .baseUrl("https://android-kotlin-fun-mars-server.appspot.com/") .addConverterFactory( Json.asConverterFactory( MediaType.get("application/json; charset=UTF8") ) ) .build() val service = retrofit.create(MarsService::class.java) // MarsServiceを返す }
「JSON⇒クラスのオブジェクト変換」にKotlin Serializationを使うので、Builder#addConverterFactory( )でSerializationのコンバーターを指定します。これで、Kotlin SerializationがRetrofitへ組み込まれます。
アクセスの実行
構築したWeb APIを使って、Webサーバーへアクセスします。
ネットを介するデータのやり取りは、非常に時間がかかる(プログラムの実行に比べて)処理なので、ワーカスレッドで行うことが強要されます。
scope.launch(Dispatchers.IO) { val _photos = MarsApi.service.getPhotos() _photos.forEach { Log.i(TAG, "(Serialization) Retrofit Demo Photo = ${it}") } }
(Serialization) Retrofit Demo Photo = Photo(id=424905, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631300503690E01_DXXX.jpg) (Serialization) Retrofit Demo Photo = Photo(id=424906, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg) (Serialization) Retrofit Demo Photo = Photo(id=424907, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631290503689E01_DXXX.jpg) (Serialization) Retrofit Demo Photo = Photo(id=424908, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg) (Serialization) Retrofit Demo Photo = Photo(id=424909, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631280503688E0B_DXXX.jpg) ...
Appendix:Coilで画像のダウンロード
Android用に開発されたCoilという画像読み込みライブラリを使うと、画像のダウンロードが簡単になります。
「app/build.gradle」へ、次の設定を行います。
... dependencies { ... // An image loading library for Android and Compose Multiplatform. implementation("io.coil-kt.coil3:coil-compose:3.1.0") implementation("io.coil-kt.coil3:coil-network-okhttp:3.1.0") }
この記事内で作成したRetrofitのサンプルをそのまま使っています。
private val marsPhotos = mutableStateOf(listOf<Photo>()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { AppTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> PhotoPanel( marsPhotos = marsPhotos.value, modifier = Modifier.padding(innerPadding) ) } } } lifecycleScope.launch(Dispatchers.IO) { marsPhotos.value = MarsApi.service.getPhotos() } }
AsyncImageは画像の読み込み機能を持ったCompose UIです。URLに従い、ネットワーク経由で画像を読み込みます。
@Composable fun PhotoPanel( marsPhotos: List<Photo>, modifier: Modifier = Modifier ) { val _imgIndex = remember { mutableStateOf(0)} var _imgSrc = if(marsPhotos.isNotEmpty()) marsPhotos[_imgIndex.value].imgSrc else R.drawable.noimages_360x240 AsyncImage( model = _imgSrc, contentDescription = null, modifier = modifier .clickable { _imgIndex.value = if(_imgIndex.value >= marsPhotos.size) 0 else _imgIndex.value + 2 }, placeholder = painterResource(R.drawable.loading_360x240) ) }


関連記事: