はじめに
最近自分の周りでよく聞くRetrofitを使用して、API通信で取得したデータをAndroidアプリの画面に表示させてみました。
本記事はそのサンプルコード及び備忘になります。
変な箇所とかあれば、遠慮なく指摘いただけますと嬉しいです。
Retrofitとは
HTTPクライアント通信を行うためのライブラリです。
これによりアプリからサーバと通信して情報を取得することが可能です。
実装
実装について記載していきます。
API準備
コーディングに移る前にまずは取得するAPIの準備をします。
今回はOpenWeatherという天気情報を取得できるAPIを使用して実装していきます。
Manifest
天気APIを使用する際はインターネット通信を行うため、以下をManifestに追記します。
<uses-permission android:name="android.permission.INTERNET" />
build.gradle
Retrofitを使用するために、以下をbuild.gradle(app)に追記します。
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
モデル
天気情報(場所・天気)を格納するモデルを用意します。
data class WeatherModel(
val placeName: String,
val weatherInfo: String,
) {
val place: String = placeName
val weather: String = weatherInfo
}
APIインターフェース
Retrofitで使用するAPIのインターフェースを用意します。
@QueryではURLに渡すパラメータを指定しています。
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にして、先ほど作成したモデルを更新します。
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を用意します。
class WeatherRepository(
private val weatherRemoteDataSource: WeatherRemoteDataSource
) : IWeatherRepository {
override suspend fun featchWeather(): WeatherModel? = weatherRemoteDataSource.featchWeather()
}
RepositoryのInterfaceも用意しました。
interface IWeatherRepository {
suspend fun featchWeather(): WeatherModel?
}
Presenter
Viewに取得データを渡すPresenterを用意します。
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を用意します。
interface WeatherContract {
interface View {
fun setWeatherInfo(weatherModel: WeatherModel)
fun showDialog()
fun setProgressBar(isShow: Boolean)
}
interface Presenter {
fun getWeather()
}
}
Activity
Activityクラスを用意します。
Presenterから受け取った天気情報をUIに表示します。
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
}
}
動作確認
「天気情報取得」で天気情報を取得し、「初期化」で表示を初期化します。
つまづいたところ
実装の中でいくつかつまづいたので備忘として書いていきます。
・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