Retrofit+MoshiでWebサービスへアクセス

投稿日:  更新日:

RetrofitはRESTに準拠したWeb APIです。

このRetrofitとMoshiを使って、Webサービスへアクセスする方法を、まとめました。

サンプルはAndroidのコードラボと同じ内容です。私はコードラボが理解し難かったので、不要な部分をそぎ落として、Retrofitに的を絞って説明しています。

※環境:Android Studio Ladybug Feature Drop | 2024.2.2
    Kotlin 2.0.0
    Moshi 1.15.2
    Retrofit2 2.11.0
    Coil 3.1.0

スポンサーリンク

Retrofitとは

「Retrofitとは」を説明するには、Retrofitの使われる背景が重要です。WebサービスからRetrofitへ至る話になるので、少々長くなります。

WebサービスとWeb API

200X年の中ごろから、インターネット上で様々なサービスを提供するサイト(企業、団体や組織など)が登場してきました。例えば、提供されるサービスとは「天気予報」「Web検索」「地図情報」「翻訳機能」などです。

サービスを提供する仕組みに、Webサーバー(HTTPサーバー)とHTTPプロトコルが使われる事から、「Webサービス」と呼ばれています。

Webサービスの例

これらのサービスを利用し易いように、サービスを利用する方法の記述されたプログラムライブラリーが、サイト側から提供されます。このライブラリを「Web API」といいます。

Web APIを使えば、自身が開発するアプリケーションへWebサービスを手軽に組み込めます。

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クライアント)になります。

Moshiを組み込めば、Webサーバーから取得したJSONデータ(サービスデータ)を、データクラスのオブジェクトに変換して、プログラムへ取り込むことが出来ます。

Moshiの使用事例(Retrofitの場合)

とても、便利です。

スポンサーリンク

環境設定

「app/build.gradle」へ、次の設定を行います。

plugins {
    ...
    id("com.google.devtools.ksp") version "2.0.0-1.0.23"
}

android { ... }

dependencies {
   ...

    // moshi for parsing the JSON format
    val moshi_version = "1.15.2"
    implementation ("com.squareup.moshi:moshi:$moshi_version")
    implementation ("com.squareup.moshi:moshi-kotlin:$moshi_version")
    ksp ("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version")

    // 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")
}

※Moshiの環境設定については「Moshiで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)を持ち、同じデータが配列(またはリスト)になっています。

データクラスで表現すると、以下の通りです。

@JsonClass(generateAdapter = true)
data class Photo(
    val id: String,
    @Json(name = "img_src") val imgSrc: String  // JSONのキーに別名を定義
)

「JSON⇒データクラスのオブジェクト変換」にMoshi(Kotlin Codegenで行う)を使うので、@JsonClassを付けておきます。
※Moshiについては「Moshiで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 moshi = Moshi.Builder()
	    // 必要なアダプターを追加
	    // .add(アダプター)
        .build()
    private val retrofit = Retrofit.Builder()
        .baseUrl("https://android-kotlin-fun-mars-server.appspot.com/")
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .build()

    val service = retrofit.create(MarsService::class.java) // MarsServiceを返す
}

「JSON⇒データクラスのオブジェクト変換」にMoshi(Kotlin Codegenで行う)を使うので、Builder#addConverterFactory( )でMoshiのコンバーターを指定します。これで、MoshiがRetrofitへ組み込まれます。

コンバーターの種類
Moshi以外のコンバーターもあります。

Retrofitで利用可能なコンバーター

ちなみに、追加すべきMoshiのアダプターが無ければ、ユーザーのプログラム上で、Moshiのオブジェクトをわざわざ作る必要はありません。
※Moshiのアダプターについては「MoshiのカスタムJsonAdaperを作成」を参照

#create( )関数内で、作成してくれるからです。

public final class MoshiConverterFactory extends Converter.Factory {
  public static MoshiConverterFactory create() {
    return create(new Moshi.Builder().build());
  }

  @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
  public static MoshiConverterFactory create(Moshi moshi) {
    if (moshi == null) throw new NullPointerException("moshi == null");
    return new MoshiConverterFactory(moshi, false, false, false);
  }
  
  ...
}

アクセスの実行

構築したWeb APIを使って、Webサーバーへアクセスします。

ネットを介するデータのやり取りは、非常に時間がかかる(プログラムの実行に比べて)処理なので、ワーカスレッドで行うことが強要されます。

    scope.launch(Dispatchers.IO) {
        val _photos = MarsApi.service.getPhotos()
        _photos.forEach {
            Log.i(TAG, "(Moshi) Retrofit Demo Photo = ${it}")
        }
    }
(Moshi) Retrofit Demo Photo = Photo(id=424905, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631300503690E01_DXXX.jpg)
(Moshi) Retrofit Demo Photo = Photo(id=424906, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg)
(Moshi) Retrofit Demo Photo = Photo(id=424907, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631290503689E01_DXXX.jpg)
(Moshi) Retrofit Demo Photo = Photo(id=424908, imgSrc=https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg)
(Moshi) 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)
    )
}
【R.drawable.noimages_360x240】
画像はありあません
【R.drawable.loading_360x240】
画像を読み込み中
スポンサーリンク

関連記事:

Kotlinで利用可能なJSONライブラリーには「GSON, Jackson, Moshi, Kotlin Serializationなど」があります。 始めの3つはJavaがベースです。その中のMoshiは、拡張機能によりKotlinとの親和性が高められています。 Kotlin SerializationはJSON以外(Protobuf, CBOR, Hocon, Properties )のフォーマットも扱えます。フォーマットを扱うというよりも、シリアル化の機能を重視したライブラリーのようです。 プログラム間でデータを受け渡す際に用いるのであれば、Kotlin Serializationが最も適しているかも知れません。 Kotlin Serializationに興味を引かれますが、後の機会に置いといて... 今回は、Moshiについて、まとめます。 ※環境:Android Studio Ladybug Feature Drop | 2024.2.2     Kotlin 2.0.0     Moshi 1.15.2 ...
MoshiでJSONをパース(JSONの記述⇔データクラスのオブジェクト)する場合に、一般クラスは未対応です。 例えば、データクラス以外の、ユーザ定義のクラスはパース出来ません。 対応させるためには、そのクラスのカスタムJsonAdapterを作成します。 そして、相互変換する方法をプログラマー側で定義します。 カスタムJsonAdapterの作成方法をまとめます。 ※環境:Android Studio Ladybug Feature Drop | 2024.2.2     Kotlin 2.0.0     Moshi 1.15.2 ...
スポンサーリンク