1時間ごとの天気予報アプリ(Kotlin, OpenWeatherMap)


はじめに

天気予報アプリを作るのにかなり苦戦したので
忘れないようにメモとして残したいと思い書かせてもらいます。

今回はOpenWeatherMapを利用して1時間ごとの天気を取得していきます。

本当にAPIを触って天気を表示するくらいなので
見た目の地味さは許してください...

OpenWeatherMap ログイン・Key取得

まずこちらのサイトでログインまたはサインアップします。
OpenWeatherMap

その後、上部にあるメニューから「API」を選びます。
「One Call API」-> 「Subscribe」を選択し
Freeプランの「Get API Key」を選びます。
できたら、右上のメニューにある自分の名前をクリックし、「My API Keys」をクリック。
そこでKeyの列にある英数字の文字列をコピーしておきます。

つくる

build.gradle

いつも通りにプロジェクトを立ち上げます。
そのあと、build.gradle(Module...)に以下を追加してください。

  • 自分はビューバインディングを使っているためこちらを追加
build.gradle
android {
  buildFeatures {
     viewBinding = true
  }
}
  • これはよくわからない...
build.gradle
kotlin {
     experimental {
        coroutines 'enable'
     }
}

  • Coroutine処理(別のスレッドで処理するためのやつ)を使えるようにするための情報を記述。 2021/02/22での最新版(1.4.2)を使っています。 最新版を確認したい場合は[ここから]確認してください。
build.gradle
dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2"

以上を追加したらSync Nowを忘れずに

AndroidManifest.xml

インターネットに接続するための記述

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

    <application
        android:usesCleartextTraffic="true"

XML

activity_main.xmlを開き、以下のようにボタンとテキストを設置
Button -> text: WEATHER, id: weatherButton
TextView -> text: TextView, id: textView

MainActivity

ビューバインディングで必要な変数(binding)・処理(binding=...)と
天気や時刻の結果を格納する変数(resultText)、
経度緯度を入れておく変数を用意(placeLat, placeLon)
(今回は東京の場所を格納)

MainActivity.kt
class MainActivity ...

    lateinit var binding : ActivityMainBinding
    private var resultText = ""
    private var placeLat = 35.689499
    private var placeLon = 139.691711

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

ここからはボタンが押された時の処理を書いていきます。
(getWeatherNews関数は後で記述します。)

MainActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.weatherButton.setOnClickListener {
            // 天気と時刻を取得
            getWeatherNews()
            // 3秒間処理を止める
            Thread.sleep(3000)
            // 結果をtextViewに表示
            binding.textView.text = resultText

        }
    }

ここで出てきた「Thread.sleep()」ですが
まずネットワーク接続する処理はメインスレッドではできないらしいです。
そのため、別のスレッドで処理をする必要があります。
ですが、メインスレッドが終わり次第別スレッドの処理が途中だろうがその関数の処理が中断されてしまいます。
それを防ぐために一時的に処理を待つコードを追加してあげているということです。

天気予報と時刻

ではいよいよ天気予報を取得する処理を記述していきます。


   // unixtimeからフォーマットの日付に変換
    private fun unixTimeChange(unixTime: String): String {
        var sdf = SimpleDateFormat("yyyy/MM/dd HH:mm")
        var nowTime = Date(unixTime.toInt() * 1000L)
        return sdf.format(nowTime)
    }


    private fun getWeatherNews(): Job = GlobalScope.launch {
        // 結果を初期化
        resultText = ""
        // APIを使う際に必要なKEY
        var API_KEY = "................"
        // URL。場所と言語・API_KEYを添付
        var API_URL = "https://api.openweathermap.org/data/2.5/onecall?" +
                "lat=" + placeLat + "&" +
                "lon=" + placeLon + "&" +
                "lang=" + "ja" + "&" +
                "APPID=" + API_KEY
        var url = URL(API_URL)
        //APIから情報を取得する.
        var br = BufferedReader(InputStreamReader(url.openStream()))
        // 所得した情報を文字列化
        var str = br.readText()
        //json形式のデータとして識別
        var json = JSONObject(str)
        // hourlyの配列を取得
        var hourly = json.getJSONArray("hourly")

        // 十時間分の天気予報を取得
        for (i in 0..9) {
            var firstObject = hourly.getJSONObject(i)
            var weatherList = firstObject.getJSONArray("weather").getJSONObject(0)
            // unixtime形式で保持されている時刻を取得
            var time = firstObject.getString("dt")
            // 天気を取得
            var descriptionText = weatherList.getString("description")
            resultText += "${unixTimeChange(time)}  $descriptionText \n\n"
        }

    }

補足

  • 「Date(unixTime.toInt() * 1000L)」ですが、どうやらこうしないと数値から日付に変換できないらしい(よくわからん...)
  • 「GlobalScope.launch」は別のスレッド処理を始める際に必要な記述です。
  • URLは「java.net.URL」のほうをインポートしてください
  • URLに乗せる位置情報ですが3時間ごとの予報なら都市の名前を入れればいいのですが、1時間ごとの予報だと経度緯度が必要みたいです
  • APIから得られる情報をすべて知りたい場合、API_URLのURLを実際に検索してみてください。例:「https://api.openweathermap.org/data/2.5/onecall?lat=35.689499&lon=139.691711&appid={API Key}&lang=ja」
  • 3時間ごとの予報を知りたい場合は「http://api.openweathermap.org/data/2.5/forecast?」こちらを使います。しかし、こちらから取得できるJSONファイルの中身がいろいろと違うのでその都度対応してください。

実行

それでは動作確認をしていきましょう!
アプリを開いてWEATHERボタンを押してみると
下の画像のように時刻と天気が表示されます。

最後に

皆さん無事完成できましたか??
かなり説明が少なかったり、説明の仕方がわかりずらかったりして
読んでて苦痛に感じた方もいるかもしれませんが...(汗

ですがもし、完成できた方がいたらとてもうれしいです。
自分の場合はこの段階まで来るのにかなり時間がかかってしまったほうなので
もし、自分と同じく天気予報アプリを作ろうとしている人の手助けになれたら幸いです。

参考サイト