【Kotlin/Android Studio】Retrofitの使い方!HTTP通信でAPIを取得する

この記事からわかること

  • Android Studio/KotlinRetrofit使い方
  • HTTP通信実装してAPI取得する方法
  • QiitaAPIを使用した記事取得アプリの実装方法
  • HTTPメソッドアノテーション種類
  • 使用できるコンバーターの種類
  • No type arguments expected for class Callの解決法
  • java.lang.IllegalArgumentException: Unable to create converter for class com.example.retrofit.Article for method QiitaService.fetchDataの解決法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

参考文献:公式リファレンス:Retrofit

環境

Retrofitとは?

RetrofitはAndroidアプリ開発で簡単にHTTP通信を実装できるライブラリです。開発したのはSquare社という会社でオープンソースのライブラリになっています。アプリ開発ではよく登場するAPI(RESTful API)を利用するためにはRetrofitは欠かせない存在です。

HTTP通信を実装するにはややこしいAPIのエンドポイントを把握したり、サーバーのエラー処理、レスポンスで受け取ったJSONのマッピングなど複雑でめんどくさい作業が多いです。それをRetrofitを使うことで簡単に実装できるようになっているので使い方を見ていきます。

Retrofitの実装方法

実際に実装するために今回はQiitaの記事情報を取得できるAPIを使用していきます。APIのエンドポイントは以下のURLでここから記事情報が以下のJSON形式で返ってきます。

記事(新着順20個):https://qiita.com/api/v2/items

レスポンスで受け取るJSON

[{
  "id": "記事の一意のID",
  "title": "記事のタイトル",
  "body": "記事の本文",
  "created_at": "記事の作成日時",
  "updated_at": "記事の更新日時",
  "user": {
    "id": "ユーザーの一意のID",
    "name": "ユーザー名",
    "profile_image_url": "ユーザーのプロフィール画像のURL"
  },
  // .....他にもさまざまな情報
}]

ちなみにSwiftで実装した記事は↓こちらです。

実装の流れ

最初に実装の流れを確認しておきます。

  1. 依存関係の追加
  2. インターネットの利用を可能にする
  3. JSONにマッピングするデータクラスの作成
  4. インターフェースの作成
  5. ベースURLの定義
  6. Retrofit.Builderでインスタンスの作成
  7. MainActivityで非同期による通信の実装

今回のプロジェクトの全体はGitHubに上げていますので参考にしてください。

必要になるクラスなど

RetrofitでAPI取得アプリを実装するにあたって必要となるクラスなどをまとめておきます。

依存関係の追加

RetrofitをAndroid Studio内で使用できるようにするには依存関係を追加する必要があります。また一緒にRetrofitが公式サポートしているGSON(JSONをマッピングするライブラリ)も導入しておきます。

dependencies {
  implementation "com.squareup.retrofit2:retrofit:2.9.0"

  implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
  implementation 'com.google.code.gson:gson:2.8.5'

}

インターネットの利用を可能にする

AndroidアプリからWeb通信をさせるためにはまず設定を変更する必要があります。デフォルトではインターネットアクセスが許可されていないので「AndroidManifest.xml」に<uses-permission android:name="android.permission.INTERNET" />を追加しておきます。


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
+   <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"

JSONにマッピングするデータクラスの作成

続いてJSONで返ってくるデータをKotlinで扱えるようにデータクラスを定義しておきます。ここのプロパティ名はJSONのキー値と同じにしておきます。またJSONの構造にあったようなクラス関係に定義する必要があります。


data class Article(
    val id: String,
    val title: String,
    val body: String,
    val created_at: String,
    val updated_at: String,
    val user: User
)

data class User(
    val id: String,
    val name: String,
    val profile_image_url: String
)

インターフェースの作成

続いてインターフェースを定義します。XxxxxServiceという名前にしていることが多いのでQiitaServiceとしておきました。ここでは実際にリクエストを送るメソッドを定義していきます。例えばGETメソッドを定義したい場合は以下のようになります。驚くほど簡素な定義ですがあとはRetrofitが上手いことやってくれるようです。


// ↓ちなみに自動補完だと異なるimport分が入ってしまいエラーになるので注意
// import android.telecom.Call // ×
import retrofit2.Call // ⚪︎
import retrofit2.http.GET


interface QiitaService {
    @GET("items")
    fun fetchData(): Call<List<Article>>
}

ここでポイントとなるのが@GETアノテーションとCallクラスです。GETメソッドを定義する際は@GETアノテーションをPOSTメソッドを定義する際は@POSTアノテーションという具合に付与することであとはRetrofitに任せておけます。また今回定義したfetchDataメソッドには具体的な実装は何もありませんが、これだけでHTTP通信を実装する処理は完了です。

@GETアノテーションの引数に渡しているのはAPIパスの続きです。今回ではhttps://qiita.com/api/v2/がベースとなり、itemsが変化させる可能性のあるパス部分になります。今回はパスのみでしたがクエリパラメータなども指定可能です。

ベースURLの定義

ではベースとなるURLも定義しておきましょう。これはどこでも良いですが先ほどの「QiitaService.kt」ファイルのトップレベルにpublicで定義しておきました


public const val BASE_URL = "https://qiita.com/api/v2/"

Retrofit.Builderでインスタンスの作成

「MainActivity.kt」に移ってプロパティにRetrofitインスタンスを生成しておきます。Retrofit.Builderメソッドからインスタンスを生成でき、addConverterFactoryメソッドで変換してくれるConverterFactoryを指定します。今回はGSONなのでGsonConverterFactory.create()でOKです。あとはAPIのベースURLを私、buildを実行します。


class MainActivity : AppCompatActivity() {

    private val retrofit = Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(BASE_URL)
        .build()
// 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

MainActivityで非同期による通信の実装

最後にMainActivity内からHTTP通信を実行します。今回は取得したJSONをTextViewに格納するようにしておきました。先にコードを見てみます。


 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val text:TextView = findViewById(R.id.json_text)

        // 以下Retrofit
        val service = retrofit.create(QiitaService::class.java)
        val get = service.fetchData()
        GlobalScope.launch(Dispatchers.IO) {
            var responseBody = get.execute()
            var weatherApiResponse = responseBody.body()?: throw IllegalStateException("bodyがnullだよ!")
            runBlocking {
                launch(Dispatchers.Main) {
                    text.setText(weatherApiResponse.toString())
                }
            }
        }
    }
}

まず最初にQiitaServiceクラスをクラスリテラル(::class.java)を使用して取得します。

val service = retrofit.create(QiitaService::class.java)

次に実行したいHTTP通信を定義したメソッドを呼び、Callインスタンスを取得します。

val get = service.fetchData()

実際の通信処理はメインスレッドでは行わないようにKotlin CoroutinesでDispatchers.IOを指定しexecuteメソッドで実際に実行しています。また通信処理の実行はexecuteメソッドを使用すると同期的enqueueメソッドを使用すると非同期的にHTTPリクエストが実行されます。

おすすめ記事:【Android Studio】Kotlin Coroutinesの使い方!非同期処理とスレッド

GlobalScope.launch(Dispatchers.IO) {
    var responseBody = get.execute()
    var weatherApiResponse = responseBody.body()?: throw IllegalStateException("bodyがnullだよ!")
    runBlocking {
        launch(Dispatchers.Main) {
            text.setText(weatherApiResponse.toString())
        }
    }
}

executeメソッドの結果からbodyメソッドでJSONデータを参照できるので値がnullであれば例外をスローし、nullでなければメインスレッドに切り替えてUIの更新をしています。

【Kotlin/Android Studio】Retrofitの使い方!HTTP通信でAPIを取得する

toStringメソッドを使用して無理やりテキストとして表示しましたが実際にはArticleクラスのList形式に変換されているのでそのままリサイクルビューなどに渡すだけで簡単に表示することが可能です。

返り値を必要としないPOSTメソッドを実装する

返り値を必要としないPOST送信を実装する場合も見てみます。POSTの場合は何かしらURLにデータを含ませることが多いと思いますが、パスに含めたい場合は@POST("{token}/{title}/{msg}")でURL部分を構築し、@Pathを使用して引数とパスを繋ぎます。

interface NotifyApiService {
  @POST("{token}/{title}/{msg}")
  suspend fun sendNotification(
      @Path("token") token: String,
      @Path("title") title: String,
      @Path("msg") msg: String
  ): Response<Unit>
}

呼び出す際は以下のようにすっきり記述することができます。

val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .build()

val apiService = retrofit.create(NotifyApiService::class.java)
runBlocking {
    try {
        val response = apiService.sendNotification(token, title, msg)
        if (response.isSuccessful) {
            println("Notification sent successfully")
        } else {
            println("Failed to send notification: ${response.code()}")
        }
    } catch (e: Exception) {
        println("Error: ${e.message}")
    }
}

HTTPメソッドアノテーションの種類

アノテーション 概要
@GET 引数にはパスの続きやクエリパラメータ、プレースホルダ{}を設置して変数渡しが可能
@POST 引数にはパスの続きやクエリパラメータ、プレースホルダ{}を設置して変数渡しが可能
@PUT 引数にはパスの続きやクエリパラメータ、プレースホルダ{}を設置して変数渡しが可能
@DELETE 引数にはパスの続きやクエリパラメータ、プレースホルダ{}を設置して変数渡しが可能
@FormUrlEncoded @POSTなどの前に設定することでフォームエンコードされたデータが送信
@Multipart @POSTなどの前に設定することでマルチパートで送信
@Header HTTP Headerを設定

詳細な使い方などは公式サイトを参考にしてください。

公式リファレンス:Retrofit#リクエストメソッド

エラー集

私がRetrofitを使用する際に発生したエラーを紹介しておきます。

No type arguments expected for class Call

以下のエラーはインターフェース定義時にCallのimport文が間違っていることで発生したエラーです。

No type arguments expected for class Call

Android Studioの自動補完でimport文が記述されますが、その際にRetrofitのimport retrofit2.Callが欲しいのに別のimport android.telecom.Callが入ってしまうようです。

// import android.telecom.Call // ×
import retrofit2.Call // ⚪︎

これはimport文を書き換えることで解決しました。

java.lang.IllegalArgumentException: Unable to create converter for class com.example.retrofit.Article for method QiitaService.fetchData

以下のエラーはコンバーターが正常に指定されていない場合に発生するエラーのようです。私は最初GSONではなくScalarsConverterFactoryを使用しておりましたがこちらではなぜか以下のエラーが発生しうまくいきませんでした。

java.lang.IllegalArgumentException: Unable to create converter for class com.example.retrofit.Article for method QiitaService.fetchData

Scalarsの依存関係追加コード

implementation "com.squareup.retrofit2:converter-scalars:2.9.0"

使用できるコンバーターの種類

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index