LoginSignup
0
0

Retrofit使ってみた

Last updated at Posted at 2024-01-22

はじめに

最近自分の周りでよく聞くRetrofitを使用して、API通信で取得したデータをAndroidアプリの画面に表示させてみました。
本記事はそのサンプルコード及び備忘になります。
変な箇所とかあれば、遠慮なく指摘いただけますと嬉しいです。

Retrofitとは
HTTPクライアント通信を行うためのライブラリです。
これによりアプリからサーバと通信して情報を取得することが可能です。

実装

実装について記載していきます。

API準備
コーディングに移る前にまずは取得するAPIの準備をします。
今回はOpenWeatherという天気情報を取得できるAPIを使用して実装していきます。
image.png

Manifest
天気APIを使用する際はインターネット通信を行うため、以下をManifestに追記します。

AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />

build.gradle
Retrofitを使用するために、以下をbuild.gradle(app)に追記します。

build.gradle
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'

モデル
天気情報(場所・天気)を格納するモデルを用意します。

WeatherModel.kt
data class WeatherModel(
    val placeName: String,
    val weatherInfo: String,
) {
    val place: String = placeName
    val weather: String = weatherInfo
}

APIインターフェース
Retrofitで使用するAPIのインターフェースを用意します。
@QueryではURLに渡すパラメータを指定しています。

WeatherService.kt
interface WeatherService {
    //生成するURL:https://api.openweathermap.org/data/2.5/weather?lang=ja&q=tokyo&appid=*****************
    @GET("data/2.5/weather")
    fun loadRepos(
        @Query("lang") lang: String,
        @Query("q") q: String,
        @Query("appid") appid: String
    ): Call<ResponseBody>
}

DataSource
天気APIを叩いて情報取得する処理を実装します。
API通信して取得したデータはJsonにして、先ほど作成したモデルを更新します。

WeatherRemoteDataSource.kt
class WeatherRemoteDataSource {
    //APIキーとURLの定義
    private val apiKey: String = "*****************"
    private val mainURL: String = "https://api.openweathermap.org"

    private val retrofit = Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(mainURL)
        .build()

    private val service = retrofit.create(WeatherService::class.java)
    private val loadRepos = service.loadRepos("ja", "tokyo", apiKey)

    fun featchWeather(): WeatherModel? = featchWeatherInfo()

    private fun featchWeatherInfo(): WeatherModel? {
        runCatching {
            loadRepos.clone().execute()
        }.onSuccess { responce ->
            if (responce.isSuccessful) {
                responce.body()?.string()?.let {
                    val jsonObj = JSONObject(it)

                    // モデルに取得したJsonの値を代入
                    return WeatherModel(
                        jsonObj.getString("name"),
                        jsonObj.getJSONArray("weather").getJSONObject(0).getString("description"),
                    )
                }
            } else {
                return null
            }
        }.onFailure {
            return null
        }
        return null
    }
}

Repository
DataSourceで更新したモデル情報を渡すRepositoryを用意します。

WeatherRepository.kt
class WeatherRepository(
    private val weatherRemoteDataSource: WeatherRemoteDataSource
) : IWeatherRepository {
    override suspend fun featchWeather(): WeatherModel? = weatherRemoteDataSource.featchWeather()
}

RepositoryのInterfaceも用意しました。

IWeatherRepository.kt
interface IWeatherRepository {
    suspend fun featchWeather(): WeatherModel?
}

Presenter
Viewに取得データを渡すPresenterを用意します。

WeatherPresenter.kt
class WeatherPresenter(
    private val view: WeatherContract.View,
) : WeatherContract.Presenter {

    // Repositoryを指定
    private var weaherRepository: WeatherRepository = WeatherRepository(WeatherRemoteDataSource())

    // アプリ起動したら天気情報取得を実施
    init {
        getWeather()
    }

    // 天気取得処理(取得成功したら取得情報をUIに反映し、失敗したら失敗ダイアログを表示)
    override fun getWeather() {
        var result: WeatherModel? = null
        CoroutineScope(Dispatchers.IO).launch {
            runCatching {
                withContext(Dispatchers.Main) {
                    view.setProgressBar(true)
                }
                result = weaherRepository.featchWeather()
            }.onSuccess {
                result?.let {
                    withContext(Dispatchers.Main) {
                        view.setProgressBar(false)
                        view.setWeatherInfo(it)
                    }
                    return@let
                }
            }.onFailure {
                withContext(Dispatchers.Main) {
                    view.showDialog()
                    view.setProgressBar(false)
                }
            }
        }
    }
}

ViewとPresenterのやりとりを定義したinterfaceを用意します。

WeatherContract.kt
interface WeatherContract {
    interface View {
        fun setWeatherInfo(weatherModel: WeatherModel)
        fun showDialog()
        fun setProgressBar(isShow: Boolean)
    }

    interface Presenter {
        fun getWeather()
    }
}

Activity
Activityクラスを用意します。
Presenterから受け取った天気情報をUIに表示します。

MainActivity.kt
class MainActivity : AppCompatActivity(), WeatherContract.View {

    // Presenterを指定
    private lateinit var presenter: WeatherContract.Presenter

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

        presenter = WeatherPresenter(this)

        val btnGetWeather: Button = findViewById(R.id.btnGetWeather)
        val btnClear: Button = findViewById(R.id.btnClear)

        //東京ボタンクリック時の処理
        btnGetWeather.setOnClickListener {
            presenter.getWeather()
        }

        //クリアボタンクリック時の処理
        btnClear.setOnClickListener {
            val place: TextView = findViewById(R.id.placeName)
            val weather: TextView = findViewById(R.id.weather)
            place.text = "場所"
            weather.text = "天気"
        }
    }

    //画面のテキストにJsonの取得結果(天気情報)を表示
    override fun setWeatherInfo(weatherModel: WeatherModel) {
        val place: TextView = findViewById(R.id.placeName)
        val weather: TextView = findViewById(R.id.weather)
        place.text = "${weatherModel.place}の天気は"
        weather.text = "${weatherModel.weather}です。"
    }

    // 取得失敗時のエラーダイアログ表示処理
    override fun showDialog() {
        AlertDialog.Builder(this)
            .setTitle("取得失敗")
            .setMessage("天気情報の取得に失敗しました")
            .setPositiveButton("OK") { dialog, _ ->
                dialog.dismiss()
            }
            .show()
    }

    // 情報取得中のプログレスバー表示処理
    override fun setProgressBar(isShow: Boolean) {
        val progressBar: ProgressBar = findViewById(R.id.progressBar)
        progressBar.isVisible = isShow
    }
}

動作確認

「天気情報取得」で天気情報を取得し、「初期化」で表示を初期化します。
Videotogif.gif

つまづいたところ

実装の中でいくつかつまづいたので備忘として書いていきます。
・API準備
APIのインタフェースを用意するとき、構築するURLのクエリ(?以降)の指定に地味に引っかかりました。@Queryをつけてなかったり、クエリ名を間違えていて正しいURLになってなかったり。

↓の形でクエリ名を指定するとうまく動きました。

// 構築URL:https://sample.com/test?a=***&b=***&c=***
// ↑のクエリ(?以降)を指定する書き方
@Query("a") a: String,
@Query("b") b: String,
@Query("c") c: String

・Jsonのパースについて
取得したJsonのパースが失敗していたため、Gsonというライブラリを使ってみました。
これでjsonをjava(Kotlin)で扱えるようになります。

    private val retrofit = Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create()) // ここでGsonを使用
        .baseUrl(mainURL)
        .build()

参考

https://square.github.io/retrofit/
https://smile-jsp.hateblo.jp/entry/android/retrofit-basic
https://qiita.com/kaba/items/355363645b8a9e47e2da

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0