簡単な天気アプリをKotlin初心者が実装した話

こんにちはmotochikaです。
1ヶ月ほど前から趣味でKotlinの勉強をしています。
最近、HTTP通信を利用した簡単な天気アプリを便利なライブラリを使いながら作ったので記事にしてみました。

APIはLiveDoor天気情報のWeather Hacksです。
~~http://weather.livedoor.com/weather_hacks/webservice~~
*WeatherHacksは2020/7/31を以って終了してしまったそうです。

##デモ画面
<img width="50%" alt="picture2.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344938/d5323cd9-df25-a3a4-a8d0-83b47acbe79e.jpeg">

例えば明日の天気のボタンを押すと
<img width="50%" alt="picture3.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344938/5a4678e5-1cfb-e50c-a6e5-228ca3e40bb1.jpeg">

画面に表示された地域はほんの一例で、お天気APIに対応している地域ならどこでも表示できます。

##使用したライブラリ
####Retrofit2
APIをJava/Kotlinインターフェースに変換するライブリです。
[公式](https://square.github.io/retrofit/)
retrofit2とmoshiのコンバータを導入します。

```gradle:build.gradle
implementation 'com.squareup.retrofit2:retrofit:retrofit2の現バージョン'
implementation 'com.squareup.retrofit2:converter-moshi:retrofit2の現バージョン'
```

####Moshi
Jsonのパースを行うライブラリです。
kotlinクラスへのサポートを提供しています。
[公式](https://github.com/square/moshi)

```gradle:build.grade
implementation "com.squareup.moshi:moshi:moshiの現バージョン"
implementation "com.squareup.moshi:moshi-adapters:moshiの現バージョン"
implementation "com.squareup.moshi:moshi-kotlin:moshiの現バージョン"
kapt "com.squareup.moshi:moshi-kotlin-codegen:moshiの現バージョン"
```
最後の`kapt`ですがkotlin-annotation-processing toolsといって、アノテーションを使いコードを生成できるツールです。retrofit2を使う際に必要です。

```gradle:build.gradle
apply plugin: 'kotlin-kapt'
```
最初にこう書いてあげることも忘れずに。
###内容
はじめにインターフェースを作成します。

```kotlin:WeatherApi.kt

import com.example.motochika.getweather.api.WeatherInfo
import retrofit2.http.GET
import retrofit2.http.Query

interface WeatherApi {
    //"forecast/webservice/json/v1"にGETリクエストをする関数
    @GET("/forecast/webservice/json/v1")
    suspend fun getWeatherInfo( 
        @Query("city")
        city: String
    ): WeatherInfo
}

```

Retrofit2では$@$GET(規定URL以降の部分)でGETリクエスト先のAPIを指定できます。
http://weather.livedoor.com/forecast/webservice/json/v1?city=400040

関数`getWeatherInfo`では引数をリクエストに含めるパラメータとし、戻り値はJSONデータを受け取るデータクラスWeatherInfoとしています。

次のクラス`ApiFactory`では上記のインターフェースをインスタンス化して返す関数`weatherApi`を定義しています。

```kotlin:ApiFactory.kt
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.create

class ApiFactory {

    fun weatherApi(): WeatherApi {

        val BASE_URL = "http://weather.livedoor.com"

        val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()

        val retrofit = Retrofit.Builder().baseUrl(BASE_URL)
            .addConverterFactory(MoshiConverterFactory.create(moshi))
            .build()

        return retrofit.create(WeatherApi::class.java)

    }

}
```
関数`weatherApi`では生成したMoshiのインスタンスと規定URLを元にしてretrofitインスタンスを生成します。こうしてあるプログラムで`ApiFactory`のインスタンスを生成した際に関数`weatherApi`を中継して先ほどの関数`getWeatherInfo`を呼び出すことができます。

次のコードのwhen文の内部をみてください。


```kotlin:Result.kt

    suspend fun getWeather(id: String, day: String) = withContext(Dispatchers.IO) {
        val http = ApiFactory()
  
        when (day) {
            "今日" -> http.weatherApi().getWeaterInfo(id).forecasts.find {
                it.dateLabel == "今日"
            }?.telop
            "明日" -> http.weatherApi().getWeaterInfo(id).forecasts.find {
                it.dateLabel == "明日"
            }?.telop
            "明後日" -> http.weatherApi().getWeaterInfo(id).forecasts.find {
                it.dateLabel == "明後日"
            }?.telop

            else -> "日なし"
        }

    }

    suspend fun setWeatherText(day: String) {
   
        weather_result.setText(
            "札幌の${Day}の天気：" + setWeather("016010", Day) + "\n" +
                    "旭川の${day}の天気：" + getWeather("012010", day) + "\n" +
                    "函館の${day}の天気：" + getWeather("017010", day) + "\n" +
                    "仙台の${day}の天気：" + getWeather("040010", day) + "\n" +
                    "米沢の${day}の天気：" + getWeather("060020", day) + "\n" +
                    "新潟の${day}の天気：" + getWeather("150010", day) + "\n" +
                    "会津若松の${day}の天気：" + getWeather("070030", day) + "\n" +
                    "東京の${day}の天気：" + getWeather("130010", day) + "\n" +
                    "横浜の${day}の天気：" + getWeather("140010", day) + "\n" +
                    "名古屋の${day}の天気：" + getWeather("230010", day) + "\n" +
                    "大阪の${day}の天気：" + getWeather("270000", day) + "\n" +
                    "京都の${day}の天気：" + getWeather("260010", day) + "\n" +
                    "広島の${day}の天気：" + getWeather("340010", day) + "\n" +
                    "熊本の${day}の天気：" + getWeather("430010", day) + "\n"
        )

    }
}

```
関数`getWeatherInfo`に地域idを投げるだけで、戻り値のデータクラスについて様々な処理ができます。そして関数`getWeather`では地域idと今日〜明後日というキーワードを元に天気予報を返しています。


余談ですが `weather_result.setText`について、weather_resultはTextViewのid名です。
kotlin-android-extensionsを使用すると、findViewById()を使わなくても直接id名が使えます。小さいことですがとても感動しました。

##最後に
こうして記事にすると自分で書いたコードでもまだ理解できていない場所が洗い出されるので、Qiitaに投稿するのはとても良いことだなぁと思いました。

サポートしてくれてありがとうございます。@yt8492