【はじめてのKotlinプログラミング(29)】お天気アプリ~APIと非同期処理(コルーチン/Coroutine)~

はじめに

kotlinで、外部からお天気情報を取得して、それを自分のアプリに表示する
お天気予報アプリを作ってみたいと思います。

今回、東京と沖縄のボタンを用意しました。
東京ボタンを押すと、東京の天気が表示されますし、
沖縄ボタンを押すと、沖縄の天気が表示されます。
でクリアボタンで元に戻ると。いうシンプルなお天気アプリです。

▼お天気情報を取得するテンプレートはこちら

https://api.openweathermap.org/data/2.5/weather?lang=ja&q=都市名&appid=ここにAPIキー

動画

****************もくじ(再生時間)****************

■はじめに
 -同期処理
 -非同期処理
 -非同期処理コード

■xml(10:35~)

■準備
 -準備1:パーミション追加(18:20~)
 -準備2:ライブラリ追加(19:50~)
 -準備3:APIキー取得(21:45~)

■1)東京ボタンを押した時(29:35~)

■2)非同期処理
 -weatherTaskでコルーチン(Coroutine)を作る(34:40~)
 -処理3と処理4の呼び出しコード(40:35~)

■3)ワーカースレッド
 -http通信とリターン(43:00~)
 -responseの中身。withContext(47:45~)
 -try(やりたい処理にトライ)(51:20~)
 -catch(エラーをキャッチ)(58:40~)

■4)TextViewに表示
 -流れの確認(1:01:40~)
 -Viewの取得(1:03:20~)
 -tvに東京を表示(1:08:00~)
 -tvにお天気を表示(1:10:50~)
 -tvに最高気温、最低気温(1:13:45~)

■5)沖縄ボタン(1:18:10~)

■6)クリアボタン(1:20:10~)

コード

▼AndroidManifest.xmlに追加する、インターネットのパーミション追加

<uses-permission android:name=”android.permission.INTERNET”/>

▼buid.gradleのdependenciesに追加するやつ

implementation “org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0”
implementation “androidx.lifecycle:lifecycle-runtime-ktx:2.3.0”

▽コルーチンのリリースページ
https://github.com/Kotlin/kotlinx.coroutines/releases

▽Android デベロッパー「Lifecycle」
https://developer.android.com/jetpack/androidx/releases/lifecycle


▼activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="【お天気アプリ】"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnTokyo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="東京"
        app:layout_constraintEnd_toStartOf="@+id/btnOkinawa"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle" />

    <Button
        android:id="@+id/btnOkinawa"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="沖縄"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/btnTokyo"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle" />

    <TextView
        android:id="@+id/tvCityName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="都市名"
        android:textSize="34sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnTokyo" />

    <TextView
        android:id="@+id/tvCityWeather"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="都市の天気"
        android:textSize="34sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvCityName" />

    <TextView
        android:id="@+id/tvMax"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="最高気温"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvCityWeather" />

    <TextView
        android:id="@+id/tvMin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="最低気温"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvMax" />

    <Button
        android:id="@+id/btnClear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:text="CLEAR"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

▼MainActivity.kt

package com.example.weatherapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.URL

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //0)準備(APIキーと、URL(の基本部分)を定義)
        val apiKey = "あなたのAPIキー"
        val mainUrl = "https://api.openweathermap.org/data/2.5/weather?lang=ja"

        //0)準備 viewを取得
        val btnTokyo: Button = findViewById(R.id.btnTokyo)
        val btnOkinawa: Button = findViewById(R.id.btnOkinawa)
        val tvCityName: TextView = findViewById(R.id.tvCityName)
        val tvCityWeather: TextView = findViewById(R.id.tvCityWeather)
        val tvMax: TextView = findViewById(R.id.tvMax)
        val tvMin: TextView = findViewById(R.id.tvMin)
        val btnClear: Button = findViewById(R.id.btnClear)

        //1)btnTokyoが押されたら
        btnTokyo.setOnClickListener {
            //[1-1]東京のお天気URLを取得して
            //val weatherUrl ="https://api.openweathermap.org/data/2.5/weather?lang=ja&q=tokyo&appid=あなたのAPIキー"
            val weatherUrl = "$mainUrl&q=tokyo&appid=$apiKey"

            //[1-2]そのURLを元に得られた情報の結果を表示
            //2)コルーチンを作る⇒3)HTTP通信(ワーカースレッド)⇒4)お天気データ表示(メインスレッド)
            weatherTask(weatherUrl) //中身は「2」へ
        }

        //5)btnOkinawaが押されたら
        btnOkinawa.setOnClickListener {
            //[5-1]沖縄のURLを取得して
            val weatherUrl = "$mainUrl&q=okinawa&appid=$apiKey"

            //[5-2]そのURLを元に得られた情報の結果を表示
            weatherTask(weatherUrl) //中身は「2」へ
        }

        //6)クリアボタンで元に戻す
        btnClear.setOnClickListener {
            tvCityName.text="都市名"
            tvCityWeather.text="都市の天気"
            tvMax.text="最高気温"
            tvMin.text="最低気温"
        }
    }

    //2)weatherTask()の中身
    private fun weatherTask(weatherUrl: String) {
        //コルーチンスコープ(非同期処理の領域)を用意
        //GlobalScope.launch(Dispatchers.Main,CoroutineStart.DEFAULT){
        lifecycleScope.launch {
            //3)HTTP通信(ワーカースレッド)
            val result = weatherBackgroundTask(weatherUrl)

            //4)3を受けて、お天気データ(JSONデータ)を表示(UIスレッド)
            weatherJsonTask(result)

        }
    }

    //3)HTTP通信(ワーカースレッド)の中身(※suspend=中断する可能性がある関数につける)
    private suspend fun weatherBackgroundTask(weatherUrl:String):String{
        //withContext=スレッドを分離しますよ、Dispatchers.IO=ワーカースレッド
        val response = withContext(Dispatchers.IO){
            // 天気情報サービスから取得した結果情報(JSON文字列)を後で入れるための変数(いったん空っぽ)を用意。
            var httpResult = ""

            //  try{エラーがあるかもしれない処理を実行}catch{実際エラーがあった場合}
            try{
                //ただのURL文字列をURLオブジェクトに変換(文字列にリンクを付けるイメージ)
                val urlObj = URL(weatherUrl)

                // アクセスしたAPIから情報を取得
                //テキストファイルを読み込むクラス(文字コードを読めるようにする準備(URLオブジェクト))
                val br = BufferedReader(InputStreamReader(urlObj.openStream()))

                //読み込んだデータを文字列に変換して代入
                //httpResult =br.toString()
                httpResult = br.readText()
            }catch (e:IOException){//IOExceptionとは例外管理するクラス
                e.printStackTrace() //エラーが発生したよって言う
            }catch (e:JSONException){ //JSONデータ構造に問題が発生した場合の例外
                e.printStackTrace()
            }
            //HTTP接続の結果、取得したJSON文字列httpResultを戻り値とする
            return@withContext httpResult
        }

        return response
    }

    //4)3のHTTP通信を受けて、お天気データ(JSONデータ)を表示(UIスレッド)の中身
    private fun weatherJsonTask(result:String){
        val tvCityName: TextView = findViewById(R.id.tvCityName)
        val tvCityWeather: TextView = findViewById(R.id.tvCityWeather)
        val tvMax: TextView = findViewById(R.id.tvMax)
        val tvMin: TextView = findViewById(R.id.tvMin)

        // まずは「3」で取得した、JSONオブジェクト一式を生成。
        val jsonObj =JSONObject(result)

        // JSONオブジェクトの、都市名のキーを取得。⇒tvに代入して表示
        val cityName =jsonObj.getString("name")
        tvCityName.text =cityName

        // JSONオブジェクトの、天気情報JSON配列オブジェクトを取得。
        val weatherJSONArray =jsonObj.getJSONArray("weather")
        // 現在の天気情報JSONオブジェクト(配列の0番目)を取得。
        val  weatherJSON =weatherJSONArray.getJSONObject(0)
        // お天気の説明(description)を取得。
        val weather =weatherJSON.getString("description")
        // TextViewに、お天気結果を表示
        tvCityWeather.text =weather

        //JSONオブジェクトの、mainオブジェクトを取得
        val main = jsonObj.getJSONObject("main")
        //tvMaxに最高気温を表示
        tvMax.text ="最高気温:${main.getInt("temp_max")-273}℃"
        //tvMinに最低気温を表示"
        tvMin.text ="最低気温:${main.getInt("temp_min")-273}℃"
    }

}

お天気情報を取得するテンプレートURLはこちら

https://api.openweathermap.org/data/2.5/weather?lang=ja&q=都市名&appid=ここにAPIキー

テキスト

今回の主な学習のテーマは、APIの続きなんですが。

主な登場人物としては、APIそれから、
前回紹介のJSON。外部から取得した時のデータ形式ですね。

それからもう一人、非同期処理、っていう、新しい登場人物が出てきます。
ちなみに、kotlinでは非同期処理を『コルーチン』というのが推奨されていますので、
コルーチンって言葉が出てきたら非同期処理のことなんだなって思って下さい。

てことで、これらを使ってお天気アプリを作ってみたいと思います。

で、非同期処理を説明する前に、そもそも「同期処理」ってなんやってところを少し解説します。

同期処理とは?

まずは非同期処理の前に、そもそも同期処理がどういうものかについて解説します。

ここに、2つの作業ラインがあります。

表画面を司るのをメインスレッド。

作業ラインの、この一連の工程ことをスレッドと言います。

で、スレッドにも色々あるんですけど、大きくは2つ。
表画面に関わる作業工程、作業の流れのラインを「メインスレッド」、
あるいは、表画面のことをプログラミングではちょっとカッコつけて
UIと言ったりするので、UIスレッド、と言います。
ユーザインタフェースの略なんですけどね。ユーザが見る画面のことですが。

今までプログラミングで解説してきたのは、全て表画面に関わるのでメインスレッドでやってました。

一方で、表画面には関与せず、作業だけをする工程を別途用意する場合があります。
それを作業スレッドということで、ワーカースレッドと言ったりします。

今回でいうと、API、つまり外部へ情報を要請して、
そこで応答があればデータを受け取って、みたいな。
スーパーでいうと、仕入れとか、食品加工工場、みたいなものをイメージしてください。
こっち(メインスレッド)が店頭で、こっちが仕入れて、加工すると。

で、同期処理って言うのは、これら2つのラインが

メインスレッドも、ワーカースレッドも、1つに繋がってる、みたいな状態です。
ここ繋がってるんで、ずーっと水が流れてます。

今出力結果がアルファだとして、
新しくベータを入力した、とします。
そうすると、ベータ、ベータ
ベータ、ベータ、と繋がって、ベータ用の加工をやって
出力結果もベータにかわると。
一連の流れで、全て同じタイミングでベータに変わります。

同じタイミングで処理がされるので、同期処理、と言います。
いやいや、それでええやないかと。
何が問題やねんと。いうことです。

これ、例えばですけど、ワーカースレッドの処理に、
つまり、外部との仕入れとかに、物凄く時間がかかる作業だったらどうでしょう。
ここ、一旦止まりますよね。

そうすると??
同期処理の場合、全て繋がってるので、ここが止まるとその後の処理も止まります。

特にメインスレッドは、先ほど『表画面を司るライン』って話をしました。
てことはこっち、メインスレッドが止まるということは、つまり画面がフリーズした状態になります。
これは不味いなと。

お店でいうと、加工工場で作業が止まったばっかりに、お店の店頭まで停電になる、みたいな。
それはマズいですよね。

そこで、こっちとこっちは、別々の処理にわけましょうと。

ずっと繋がってるんじゃなくって
作業ラインで作業が完了したら、メインの方で続きを再開しましょう。
っていうのが非同期処理です。

非同期処理

■非同期処理とは、ある作業を中断し、(完了したら)また再開する処理のことです。

それでは同期処理がふんわりわかったところで、
非同期処理についてみていきます。

先ほどの説明のとおり、同期処理は全部繋がっているから
もちろん良いこともあるんですけど
表画面に直接関係なければいいんですけど、
表画面の表示に直接関係してくる場合には配慮が必要なので、
そのための手段が非同期処理です。

じゃあどうするかって話なんですが
ここと、ここと直接つなぐんじゃなくって
メインは、メイン、作業は、作業、というスレッドのまま

新しい領域を作ってあげます。
道路でいうと、高速道路っていうか
下の道路と上の道路、みたいなイメージですかね。

下の道路、つまり、一般道と、高速道路はお互い干渉しません。
なので高速道路で渋滞が起きても、トラブルが起きても
一般道路は平常に流れとると、こういうわけです。

で高速道路の方で、作業が完了した場合に限って、一般道路に降りて来て
続きを再開、メインの方で続きを再開できますよ。みたいな感じになります。

まあ、なんかもう道路が出てきたり、さっきはスーパーが出て着たり、
たとえ話がもう無茶苦茶なんで、たとえとしては微妙なんですが
まあ、なんとなくの雰囲気として理解してください。

で、今出てきたこの非同期処理を行う領域のことを、
スコープといいます。
望遠鏡なんかでも、覗いて見える範囲のことをスコープっていいますよね。
あれと同じですが、領域のことをスコープ。

で、kotlinでは、非同期処理を「コルーチン」っていうのが推奨されているので
この領域のことをコルーチンの領域ということで

まあそのままですけど、コルーチンスコープと言ったりしますので用語として覚えておいてください。
非同期処理を行う領域、ってことですね。

これで非同期処理についての説明はひとまず終了です。

********************************

で、最後、
じゃあ、この領域はどうやって作るかっていうと、
非同期処理を行いたいタイミング、この図でいうと処理②のところで
スコープを.launchっていう呪文を唱えてあげます。
そうすると{ }でくくられた中が、この領域になります。

launchっていうのは、もうだいぶ日本語にもなりつつあるのでご存知の方も多いと思いますが
開始とか、発売するって意味ですね。
プロジェクトとか商品を売り出すことをローンチって、日本語でもたまに使ったりもしますよね。

そんなところですかね。あ、あと最後に1つ。

コルーチン領域の中の処理は大きく2つに分かれます。
1つは、コルーチンの中で、かつ、作業だけを行うワーカースレッドの処理。ここでは【作業①】。

それからコルーチンの中にあるんだけど、ワーカースレッドで完了したやつを受け取って、

画面表示を担うメインスレッドでの処理。この場合【処理③】。

コルーチンの中のワーカースレッド、
コルーチンの中のメインスレッド、この2つです。

処理は全部ワーカースレッドで行えばええやんって行きたいところなんですが
androidでは、画面表示に関わる操作はメインスレッドで行いましょう、
っていうルールになっているので、作業だけを行うラインと、画面表示にかかわるラインは
使い分けなければいけません。

で、前回説明のJSONデータですが、
作業ラインで、外部へアクセスして、
通信に成功したら、データを受け取る。ここで受け取るのが前回説明のJSONデータ。

ここは受け取るだけで、TextViewに表示するのは、もう1個別の関数を作って、そっちへ渡して
そっちでTextViewに表示すると、いう流れになります。

てことで、これらを踏まえた上で、
この概念図をなんとなく頭に入れながら、
今回の書いていくコードを、この図面に落とし込むとこうなります

なんとなくこの図を頭に入れながら、さっそく始めていきましょう。

xmlを作って⇒下準備

(XMLの説明は割愛)

それでは、ktに・・・と行きたいところなんですが。

今回はですね、事前に準備していただく作業、下ごしらえが3つほどあります。

■インターネットが使えるようにパーミション(権限)を追加
■それからコルーチン、さっき説明した非同期処理ですね、これが使えるようにライブラリに追加
■最後に、お天気APIキーを取得

この3点です。
なんかこう、これだけ見るとですね、まだ本題に入ってないのに
知らん人が3人も出て来て、しんどそうな印象を受けるかもしれませんが。

これはもう決まり文句というか、ただの流れ作業なので
やってる意味さえ理解すれば、深く考えずに、機械的に書いていくだけでOKです。

①インターネットパーミション追加

ではまず、1つめ。
APIで外部のネットワークに接続するための権限、これをパーミションと言いますが、
それを記述します。(プロジェクトを開く)

場所は、左サイド、manifestsの中のAndroidManifest.xml
この中に一文書きます。

これはもう、概要欄にコードのリンクを貼っておきますのでコピペでいいと思いますが
packageうんぬんかんぬん、三角カッコの下に、
この一文を追加してください。

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

②ライブラリ追加

冒頭で説明の「コルーチン」っていう非同期処理を行うためには
ライブラリっていうのを追加する必要があります。

まあ、文字通り「図書館」っていう意味で、
呪文が書かれた巻物みたいなのがライブラリには色々あると思ってください。

で、コルーチンを使うための巻物っていうのも用意されているので、
その巻物を使うと、呪文が使えるようになると、こういうわけです。

まあ、このへんも理屈はともかく流れ作業なので、コピペでOKなのですが、
プロジェクトの左側、build.gradle(Module)をクリックしてください。
そうするとファイルが出てきまして、dependenciesっていうのがあります。
そこにimplementationうんぬんかんぬん、っていう定型文を貼り付けます。
これはもう概要欄のリンクのページに掲載しておきますので、コピペでいいと思います。

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"

1つだけ補足をしておくと、このバージョンは、必要に応じて変更する必要があります。
これはどこで調べるかというと、[kotlin コルーチン リリース]で検索するとこのようなページが出てきますので、適宜ご参考ください

https://github.com/Kotlin/kotlinx.coroutines/releases

③APIキーの取得方法

こちらのOpenWeatherにアクセスして、アカウントを新規作成してください。そうするとAPIキーが発行されます。

それから、OpenWeatherのサイトでデータを取得するときのアドレスは
これがテンプレートになります。
↓↓↓
https://api.openweathermap.org/data/2.5/weather?lang=ja&q=都市名&appid=ここにAPIキー

一応あとで詳しくは説明しますが、ざっくりいうと

●前半が、みなさん共通の文字列。
●真ん中が、お天気を取得したい都市名。
●最後に、APIキー

ひとまず、この3部構成になっている、というのをふんわり頭の片隅に入れておいてください。
それでは、これら3つが準備できたところでくコードを書いていきましょう。

0)viewを取得⇒1)ボタンを押したら

まずはいつものようにidでviewを取得していきます。onCreate、波カッコ閉じるの中に書いていきます。

        //0)準備 viewを取得
        val btnTokyo:Button = findViewById(R.id.btnTokyo)
        val btnOkinawa:Button = findViewById(R.id.btnOkinawa)
        val tvCityName:TextView = findViewById(R.id.tvCityName)
        val tvCityWeather:TextView = findViewById(R.id.tvCityWeather)
        val tvMax:TextView = findViewById(R.id.tvMax)
        val tvMin:TextView = findViewById(R.id.tvMin)
        val btnClear:Button = findViewById(R.id.btnClear)

で、btnTokyoのボタンを押した時ということで、この下に

//1)btnTokyoが押されたら
btnTokyo.setOnClickListener {
}

これでbtnTokyoボタンを押したら~?っていうところまでが出来ましたので
中身をこの中に書いていきます。

************************************

と、その前に。
実はもう少しだけ0番目がありまして。この0の上にもう1つ。

先ほど取得したAPIキーを使えるように、変数の中にAPIを入れておくのが1つ。
それからURLにアクセスする時の基本部分、つまりhttpsから、ランゲージ=jaまで。
ここは、みなさん共通で同じなので、その文字列をあらかじめ変数に用意して代入しておきましょう。

        //0)準備(APIキーと、URL(の基本部分)を定義)
        val apiKey = "9126ca59f3fb7e9fe8bcf0bddaa39439"
        val mainUrl = "https://api.openweathermap.org/data/2.5/weather?lang=ja"

2-1)非同期処理をスタート

それではボタンを押した時の処理を書いていきましょう。

まず、東京ボタンが押されたら、東京のお天気URLを取得して
//[1-1]東京のお天気URLを取得して

その情報をもとに得られた結果を表示する、
//[1-2]そのURLを元に得られた情報の結果を表示

っていう段取りになります。

で、例えば、東京のお天気URLって何やっていうと、

先ほどやつですが、もう一回。ひとまず、こちらのテンプレートに従って書くと
https://api.openweathermap.org/data/2.5/weather?lang=ja&q=都市名&appid=ここにAPIキー

都市名のところを、例えばtokyoとして。
APIキーのところを自分のキーを入れる。

そうすると、はい、ちゃんと表示されました。
なのでこのアドレスを取得すればいいので
val weatherUrl とかにしてあげて
今のアドレスを入れればいいわけです・・・。

      //1)btnTokyoが押されたら
        btnTokyo.setOnClickListener {
            //[1-1]東京のお天気URLを取得して
            //val weatherUrl ="https://api.openweathermap.org/data/2.5/weather?lang=ja&q=tokyo&appid=8dc5ab91bb23929e725ee32176240014"

なんですが。

長すぎてしんどいので、先ほど作った変数を使って短くします。

        //1)btnTokyoが押されたら
        btnTokyo.setOnClickListener {
            //[1-1]東京のお天気URLを取得して
            //val weatherUrl ="https://api.openweathermap.org/data/2.5/weather?lang=ja&q=tokyo&appid=8dc5ab91bb23929e725ee32176240014"
            val weatherUrl = "$mainUrl&q=tokyo&appid=$apiKey"

今、ボタンを押して、URLを取得する、というところまでやりました。

で、このURL、つまり外部と通信するっていうことになるので
万が一止まったりしても大丈夫なように、非同期処理、つまりコルーチンの中で実行します。

そのコルーチンを起動する処理をweatherTaskという名前にしてあげます。
で、その中で、さらに2つの処理を実行します。

1つは、ワーカースレッドの方で、通信、つまりデータの仕入れ作業を行ってくれる処理。
これをweatherBackgroundTask、という関数で書いていきましょう。
バックグラウンドね。メインじゃない、裏側のスレッド、ってうことでバックグラウンドという名前にしています。

で、ここで上手くいけば、JSONデータが受け取れるので
xmlに書いたTextViewに表示していくと。

表示するのは、表画面、つまりUI操作なので、UIスレッド、メインスレッドで書いていきます。
ここの処理の名前はJSONデータをいじっていくので、weatherJsonTaskという名前にして、書いていきましょう。

この4番のところで、前回のJSONで説明した解説動画が生きてくると思います。

そんなこんなで、今回は少々長旅になりますが
1つずつやっていきましょう。

ではひとまず、中身は後で書くとして、
ボタンを押したら、weatherTaskっていう関数を書いて、
丸カッコの中に、いったんお天気URLを入れましょう。

それではktに戻っていただいて。[1-2]のところに
weatherTask()と書いて、この中に、お天気情報のURLを渡してあげましょう。
weatherTask(weatherUrl)

で、この人が何をするかというと
//2)コルーチンを作る⇒3)HTTP通信(ワーカースレッド)⇒4)お天気データ表示(メインスレッド)

コルーチンを作って、いわゆるローンチして、2つの処理、
ワーカースレッドで通信、そしてお天気情報を表示、という一連の流れをやってくれます。

この図でいうと、この2番からスタートして、2番がこの一連の流れの大枠全部になります。
で、この2番という大枠、これが会社だとしたら、3と4は、それぞれ専門部署、って感じですかね。
2番という大きな波カッコの中に入っている部署。

この位置関係は頭の中で整理しておいてください。
それでは頭の整理が出来たところで、コードに戻って・・・
2番のスコープの波カッコの中に3と4の専門部署があるってことだったので

2番の中身をかいて、
http通信を行う部署と
//⇒3)HTTP通信(ワーカースレッド)

その結果を受けて、表画面に表示するための部署を
//⇒4)お天気データ表示(メインスレッド)

書いてあげましょう。

2-2)weatherTaskでコルーチンを作る

それでは今、weatherTaskの呼び出しだけ書いているので、
これの大元というか、中身の方を書いていきましょう。

//中身は「2」へ

onCreate{}の下に、書いていきます。
//2)weatherTask()の中身

ひとまずprivateと書いて、関数の書き方は、ファンクション(fun)、関数の名前、()の波カッコ。
関数の名前はこれなので weatherTask と書いてあげて、
それから、この丸カッコの中はですね、当然、ここで受け取ったデータを展開していくので
受け皿を用意といてあげましょう。
ひとまず文字列型の引数を入れておきましょう。

で、普通は他と被らないようにした方が、当然わかりやすいので
例えばここは、簡単にurlとかでもいいんですけど、これはこれで何のURLかわからないので、
今回は合えてですね、ぱっと見、わかりやすいように、weatherUrlが入ってくるので
これと同じweatherUrlを使っちゃいましょう。

理由はですね、mainULRがあって、weatherUrlがあって、さらにここでurlとかってなるともう混乱するでしょ。
なので、これ以上新しいのは個人的には作りたくない、てのが1つ。
それからこっちはオンクリエイトの中なんで、こっちの、オンクリエイトの外で、どう使おうが使うまいが関与しません。
てことで、色々都合がいいので、シンプルに同じ名前でそろえちゃいましょうってことですね。
weatherUrl
で、型は文字列ということで :String

こっちのweatherUrlは、この波カッコの中で使っていきます。

そして、この中で、コルーチンスコープを用意、いわゆるローンチしてあげます。

//コルーチンスコープ(非同期処理の領域)を用意

まずはこの色のついたスコープ、領域を作ろうってことですね。

で、ここが1つ目の難所でして。

コルーチンスコープっていうのも色々あってですね。
1つはGlobalScopeとか、あるいはlifecycleScopeとか、
まあ、なんちゃらスコープっていうのがいくつかあるわけです。
で、結論から言うと今回はlifecycleScopeを使おうかなと思ってます。

で、lifecycleScopeはですね、ライブラリを追加してあげる必要があります。

さっき、コルーチンを追加した、build.gradleを開いてください。
で、コルーチンの記述の下に

この一文をコピペしてください。
implementation “androidx.lifecycle:lifecycle-runtime-ktx:2.3.0”

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"

ちなみにこのバージョンについてはandroidのデベロッパーLifecycleに記載があります。

https://developer.android.com/jetpack/androidx/releases/lifecycle

現在の安定板は2.2.0となっています。

それから細かい話ですが、シングルクォーテーションとダブルクォーテーションがありますが
変数を使って展開したい場合はダブルクォーテーションでいける、とかそれくらいの違いです。

とまあ、これで、lifecycleっていうコルーチンの巻物を手に入れたので
lifecycleスコープの呪文が使えるようになりました。ktに戻っていただいて・・・

lifecycleScope.launch{
}

これで、コルーチンスコープが用意できました。

2-2-2)3番と4番の呼び出しコード

で、ここに処理内容を、専門部署を2つ書いていくわけですが
ここもですね、中身は長くなるので
中身は下に関数でまとめて
呼び出しコードだけ貼り付けて、書いていくようにします。

3番目はweatherBackgroundTaskという名前にしましょう、ってことだったので
ワーカースレッドで行うほうですね

//3)HTTP通信(ワーカースレッド)
weatherBackgroundTask()

で、この中に先ほど2番で受け取った引数を
この中で展開します。

weatherBackgroundTask(weatherUrl)

そして、4番はweatherJsonTaskっていう名前にしましょうってことだったので

//4)3を受けて、お天気データ(JSONデータ)を表示(UIスレッド)
weatherJsonTask()

で、4番は、この中は、3番の結果を受けてあげなければいけません。

なので、このままだと3番が入ってこないので
3番を変数に入れてあげて

val result = weatherBackgroundTask(weatherUrl)

***********************
それを4番で受け取ってあげましょう
weatherJsonTask(result)

これで、weatherTaskでうけとったweatherUrlが

//weatherUrlに東京なら東京の、沖縄なら沖縄のアドレスが入る⇒4の中身へ

3番の中で処理されて、
それの結果をresultに代入して、4番に引き継ぐと、いう一連の流れができました。

これですね。
例えば東京のボタンが押されたら、東京のURLが2番に入ってくる。
で、それをうけて3番で、通信、アクセスを行う。
完了した場合、その結果を受け取って表示するのが4番と。

あとは、3番の処理する中身と、4番の処理する中身を書いたら完成です。

3)http接続と、リターン

それでは、2番で受け取ったURLへアクセスして
データを受け取る、という、作業スレッドの部門、3番を書いていきましょう。

でまずは、この作業が、ワーカースレッドっていう
いわゆる別のラインで作業します、っていうのを明示してあげないといけません。
そこで1つ2つ新しいキーワードが出てくるんですが、1つ目のキーワードは「suspend」っていうのを
関数につけてあげます。
これ、日本語でもサスペンダーとかっていう意味で、つるすっていう意味もあるんですけど
もう1つ「一時停止」、とか「中断する」、っていう意味もあります。

つまり、このsuspendをつけてあげることで、外部と通信するので「中断する可能性がありますよ」、
っていう意味を持たせてあげます。ちょっとやっていきましょう。

ktを開いて

2番に続けて、3番の中身を書いていきます
//3)HTTP通信(ワーカースレッド)の中身

まずは通常の関数をつくりましょう。
private  ・・・ファンクションを意味する、funとかいて
関数の名前はweatherBackgroundTask
で丸カッコの、波カッコ。
***************************************
で、この中に入って来たやつを受け取ってあげる必要があるので、
引数をわかりやすくweatherUrlとかにしてあげて、型をストリング
weatherUrl:String

weatherBackgroundTask(weatherUrl:String)
***************************************

この中で書いたことが、実行されて、2番のこのタイミングで呼び出されると、こういうわけです。
で、中身を書く前に、先ほどのsuspendを、ファンクションの前に書いてあげます

これでこの関数は中断する可能性がありますよって意味になりました。

それからですね、ここの処理、つまり3番の結果は、次の工程、4番に渡してあげる必要があります。

この処理が最後まで行ったら、もう一度戻ってきて、この中に入って、
それがresultに入って、
4番に引き継ぐ、という流れです。

なので、最後まで行ったらその結果の値を戻してくる必要があるので、
結果を戻すというのは、returnを使います。

————————————————-

書き方は、

関数の波カッコの最後にreturn と書いて、そこに続けて、返したい値を書く。

そして、値の型を指定してあげる必要があるので、
丸カッコに続けて点々で、例えば文字ならStringという風に書きます。

————————————————-

今回でいうと、
例えば、何か作業をした後で、response っていう値を返したいとします。
その場合、波カッコの最後に
return response
と書いてあげる。
型を指定してあげたいので、()カッコに続けて、データ文字列なので
:String

これで、まあ、ここに至るまでにこの上で色んな処理が行われて、
最後に response っていう変数に入ったとしましょう。

そしたらここでreturnされて、上に戻っていきます。そしてこの関数の値として入ってきて、
この関数というのは呼び出し元は2番のこれなので、要するにここに入る。
リターンされた値が、ここに入る。

で、これをresultに代入して、次の工程に渡す、という流れになります。

ちょっと今回は、上に行ったり下にいったりで目がまわるかもしれませんが、
ちょっと目が回るって方はですね、個々のコードを書く前に、
そもそも全体としてどういう流れになっているか、っていうのをまずはふんわり理解していただいて
まずは流れをつかむ。
で、そっからコードを書いた方がわかりやすいかもしれません。

で、話しをもとに戻しまして。リターンのところで。
何か作業をやって、最後にリターンってことだったので、当然この前に何か作業をします。

なので変数を用意しましょう。valとしてあげて、用意する名前は、ここでリターンで返す値ということで
response という名前にしてあげます。
で、イコールの先で処理する内容を書いてあげる。

・・・わけですが。

ここでもう1つのキーワード「withContext」てのが出てきます。
説明はあとにして、いったん正解だけ書きますね。

3-2)responsの中身(ワーカースレッド)

val response =につづけて、
withContext、丸カッコの中に、ワーカースレッドを意味する、Dispatchers.IO、と書いてあげて、波カッコ。

val response = withContext(Dispatchers.IO){
}

これ、今何をしたかというと
//withContext=スレッドを分離しますよ、Dispatchers.IO=ワーカースレッド

withContextっていうのは、スレッドを分離しますよと。
で、Dispatchers っていうのがどのスレッドで実行しますか、でIOっていうのがワーカースレッドのことなので
Dispatchers.IO っていうのでで、ワーカースレッドで実行しますよ、みたいな意味になります。

先ほどまでは、ずっとメインスレッドで作業が行われていました。
ただし、外部と通信を行う時には、もしかしたらエラーで途中で止まるかもしれない。
なので、この関数は途中で止まるかもしれませんよってことでsuspendっていうのを書いて、
withContext(Dispatchers.IO)っていうのを書いてあげることで、
処理を行う作業場を、ワーカースレッドに分離しました。これで要は、切り離しが完了したというわけです。

で、ktに戻っていただいて
この波カッコ(withContext)の中で作業したやつが、変数responseに入る。

プログラミングは上から下へ流れていくので
これが下へ流れていくと、returnで、上に戻りなさい、っていう指示になっている。

上に戻ると、resultに入って、工程4に引き続がれる、という感じになりました。

ここまではいいですかね。

で、正直、この3番の中が最大の山場なんですけど、頑張っていきましょう。

いよいよ、外部に接触して、データを受け取ると、いう作業になります。

で、ここの目的は何かっていうのをいったん整理しましょう。
http通信、いわゆる外部にアクセス、今回の場合、お天気情報サイトにアクセスして
そこから取得したデータ情報、
データ情報っていうのは前回JSONデータっていうのを説明したと思うんですけど
具体的にはこれですよ、(ブラウザ)この文字列。
このJSON形式のデータをアプリ内で受け取る。というのがここの関数の任務です。

なので、ひとまず、JSONの文字列を受け取るための変数を用意しましょう。
withContext波カッコの中に

var httpResult = “”

文字列なので””としていますが、今は空っぽで、受け皿の変数だけ準備しています。
取得したら後で代入。あとで代入するので、valではなくってvarですね。

これで、
// 天気情報サービスから取得した結果情報(JSON文字列)を後で入れるための変数(いったん空っぽ)を用意。

JSON文字列を取得したときの受け皿が用意できました。

3-3)try

それでは、外部にアクセスしていきましょう。
で、実はですね、ここはもう、細かく言い出すとキリがないので
ポイントだけ説明して、なるべく簡素化した書き方で説明したいと思います。
なので、この動画でひとまずポイントだけ理解できたら、他の詳しい解説で、もっと正しい書き方というか
精密な書き方を勉強されるといいかなと思います。

で、まず一発目から、新しい登場人物なんですが、
httpResultに続けて

// try{エラーがあるかもしれない処理を実行}catch{実際エラーがあった場合}

tryなんちゃら、catchなんちゃらっていうのを書いていきます。
まずはtry{ }
でこの中に、エラーがあるかもしれないけど、やりたい処理を書いていきます。
今回でいうと、外部URLにアクセス。
で、あとで書きますが、このtryの処理でエラーが起きた場合の受け皿を
catchの中で書いていきます。
catchは後で書いていくとして、まずはメインの tryの方。

*******************************************

とりあえず、ここで入って来たお天気アドレスweatherUrlを
ただの文字列から、URLという物体に変換します。
まあ、webサイトでいう、ただの文字列に、リンクを付けてあげるイメージだと思っておいてください。
//ただのURL文字列をURLオブジェクトに変換(文字列にリンクを付けるイメージ)

変数名をURLオブジェクト、略して urlObj とかにしてあげて、
URL()の中に、weatherUrl を入れる。
これでただの文字列が、リンク付きの文字列になったので、アクセスしやすくなりました。
val urlObj = URL(weatherUrl)

*******************************************

あとは、アクセスしたURLからAPI情報を取得します。
// アクセスしたAPIから情報を取得

で、ちょっとここはややこしいので、解説はあとにして、正解だけ先に書いていきます。
テキストファイルを読み込むクラスでBufferedReaderてのがあるので
BufferedReader略してbr。
val br =

でイコールでBufferedReader() 。これで読み込むマシンを用意したので
この中に、先ほど作ったurlObjを展開するわけですが、
文字コード読み込む準備としてInputStreamReader()っていうのを書いて、
その中で今作ったurlObjを展開するということで
InputStreamReader、丸カッコ。この中に先ほどのurlObjを展開するということで
urlObj.openStream()

ちょっと階層が深くなってわかりにくいかもしれませんが、これは簡単に説明すると
//テキストファイルを読み込むクラス(文字コードを読めるようにする準備(URLオブジェクト))
テキストファイルを読み込むクラス、
文字コードを読めるようにする準備、
URLオブジェクトを展開、というようなことをしています。

この辺の書き方は色々ややこしいので、ひとまずこまかい理屈は抜きにして
最初のうちは、なんとなくやってる意味だけでも理解してもらえればいいかなと思います。
*******************************************

さあ、これで、取得したAPIが読めました。
ここで、読み込んだデータの結果が brに入ってくるので、
あとはこれを冒頭用意した httpResult に代入してあげれば完成です。

//読み込んだデータを文字列に変換して代入

———————————
httpResult = としてあげて、今読み込んだ br 。
httpResult = br

ただしbrは、読み込んだだけで、BufferedReaderクラスなので、
テキスト型に変換してあげなければいけません。
てことで .toString() ・・・といきたいところなんですが
実はこれをやると、エミュレータを起動したときにエラーが出てしまいます。
なので、こうやりたい気持ちを抑えつつ
//httpResult = br.toString()
もう1つ別のやり方として、解析したデータを、文字として読み込むということで
readTextっていうのがあります。
readって入れても出てきますが、実はtextって入れても出てくるかと思います。

なのでこれを選択。
httpResult = br.readText()
これで、問題なく処理が行われたら、httpResultに、APIで受け取ったデータが入ってきます。

これでOK、と行きたいところなんですが、
今はただ代入しただけなので、これをresponseの方に返してあげなければいけません。
下にいったやつを、もう一回上に戻す、つまり値を返す時に使うのがreturn っていう呪文です。
なのでここにreturn。

で、これに続けて返す値、つまりhttpResultを書きたいところなんですが。
withContextの中で使っているので、ちょっと特殊なんですけど
@withContext ってつけてあげないとエラーになるので、ひとまず今回の動画では
決まり文句ということで見様見真似でこのように書いてあげてください。
ちょっと今回リターンの説明をするには特殊なので。
return@withContext httpResult

ここは
//HTTP接続の結果、取得したJSON文字列httpResultを戻り値とする
そして・・・
//⇒withContext(Dispatchers.IO)に返ってresponseに代入される

これでもう、ワーカースレッドの方は9割終わったんですけど、
今はtryで、上手く言った場合の処理を書きました。

プログラミングでは、tryに続けて、仮に上手くいかなかった場合っていうのも想定しなければいけないので
これにつづけてもうちょっとだけ書いていきましょう。

3-4)catch

それでは、ワーカースレッドの最後の仕上げ、
tryでエラーが起きたときの受け皿を一応用意しておきましょう。

といってもここはもう、決まり文句というか、
やりだしたらキリがないので、簡単に終わらせます。

4~5行だけなので、もうそっくりそのままコピペしてください。
tryの閉じカッコにつづけて
エラーが起きたら受け取ってあげるということで catch と書きます。
で丸カッコの、波カッコ。
この中にエラーが発生したときの処理を書く訳ですが、もうこのまま書いてください。

 }catch (e:IOException){//IOExceptionとは例外管理するクラス
                e.printStackTrace() //エラーが発生したよって言う
            }catch (e:JSONException){ //JSONデータ構造に問題が発生した場合の例外
                e.printStackTrace()
            }

4)tvに表示

それでは最後、今取得したデータを、
4番に引き継いで、テキストビューに表示させましょう。

で、ちょっと今回コードが複雑なので、今何をやってきたかっていうのを
この図をコードに置き換えてサラリと頭を整理します。

まずボタンがおされたら、ま、東京なら、東京のボタンが押されたら、
セットオンクリックリスナーで、押された時の処理を書く。

東京がおされたら、東京のお天気URLを取得して、
weatherTaskの中で実行される。

weatherTaskってなんやっていうと、その中身がこうなっています。
どん

2番の中で、コルーチンスコープと呼ばれる、非同期処理の領域を作る。
そしてその中で2つの処理を実行。
1つは、ワーカースレッドで、外部と通信を行う。

そしてその結果を受けて。4番のメインスレッドに渡して、表示の処理を書く。
これをこれからやります。

これが完了すると右がわの②が全て完了ということになって
②というのはもとをたどれば
ここのことなので、これが完成。

つまりスマホに表示。という流れになります。

で、今までの解説で、resultの中にデータが入ってくる、というところまで出来たっていう話です。

こんな感じで、一度俯瞰で見て頭を整理しないと、
今自分が何をやっているか迷子になるんでね、なんとなくこういう図を頭で描いておいてください。

で、最後にこれからこの④をやっていこうと、こういう訳です。
具体的には、JSONの中の、どのキーを取得して、テキストビューに表示する、
というのをやっていくんですが
ここは、前回の動画の実践編、っていう感じになります。

4-1)viewの取得

それでは最後、3番で所得したデータを、TextViewに表示していきましょう。

で2番のところ。
ここで3番のデータを受け取って、4番weatherJsonTaskの中身を書いていきます。

3番の波カッコの下に

//4)3のHTTP通信を受けて、お天気データ(JSONデータ)を表示(UIスレッド)の中身

書いていきます。

まずはprivateとしてあげて、関数を意味するfunction
そして関数名、ここではweatherJsonTaskということだったので、これをそのままコピーして
丸カッコの、波カッコ。

で、3番で入って来た値を受け取れるように引数を用意しておきましょう。
まあ、この関数の中で使うやつなんで、名前はなんでもいいんですが
今回、色々名前が膨大なので、あえて、resultっていう名前とそろえましょう。
で、型は文字列なので、:String。

で、まずはですね、テキストビューに表示させるので、
表示させるViewを取得しておきましょう。
いやいや、一番最初に0番のところで取得したやないかって思われるかもしれないんですけど
これはonCreate波カッコの中で書いているので、
この波カッコの外では使えません。

なので、こっちで使う場合には、もう一度取得してあげます。

    //4)3のHTTP通信を受けて、お天気データ(JSONデータ)を表示(UIスレッド)の中身
    private fun weatherJsonTask(result:String){
        val tvCityName: TextView = findViewById(R.id.tvCityName)
        val tvCityWeather: TextView = findViewById(R.id.tvCityWeather)
        val tvMax: TextView = findViewById(R.id.tvMax)
        val tvMin: TextView = findViewById(R.id.tvMin)

これでビューが取得できましたので、これらに表示していきましょう。
で、何をやるかなんですけど、openWeatherさんのサイトの、東京のデータがこちら。

{
	"coord": {
		"lon": 139.6917,
		"lat": 35.6895
	},
	"weather": [
		{
			"id": 801,
			"main": "Clouds",
			"description": "薄い雲",
			"icon": "02n"
		}
	],
	"base": "stations",
	"main": {
		"temp": 276.47,
		"feels_like": 273.54,
		"temp_min": 275.13,
		"temp_max": 277.98,
		"pressure": 1012,
		"humidity": 31
	},
	"visibility": 10000,
	"wind": {
		"speed": 3.13,
		"deg": 335,
		"gust": 3.58
	},
	"clouds": {
		"all": 20
	},
	"dt": 1640508890,
	"sys": {
		"type": 2,
		"id": 2001249,
		"country": "JP",
		"sunrise": 1640468954,
		"sunset": 1640504033
	},
	"timezone": 32400,
	"id": 1850144,
	"name": "東京都",
	"cod": 200
}

今、これを自分のスマホに取り込んだっていうところまで来てまして、あとは表示するだけです。
で、前回の動画で、表示させたいオブジェクトのキーを指定するって、ことでした。
で、オブジェクトが配列で囲ってある場合は、配列から中へ中へ。ってことでした。
少し思い出してきましたでしょうか。

で、前回は説明を割愛したんですが、実はこれらもろとも、一番そとに、でっかいオブジェクト、
この波カッコでくくってあるのがわかります。
この一式もろともを取得したのが3番ってことです。

なので、まずはこの「東京都」を表示したい場合は、そのキーであるnameっていうのを指定してあげる・・・

わけですが・・・

nameっていうのは、さらにこのでっかいオブジェクトに属しているのがわかります。
なので、このでっかい全体のオブジェクトの中の、
getStringでキーのnameを指定してあげる、という段取りになります。

まあ、説明するより見てもらった方が速いので、実際やっていきましょう。

4-2)tvに東京を表示

それではまず、一番外側のJSONObjectを取得しましょう。
ktに戻って

// まずは「3」で取得した、JSONオブジェクト一式を生成。

valで宣言してJSONObject、略してjsonObj、とかにしてあげましょう。
でJSONObject()の中に、ここに入って来た、result を入れてあげる。

これで3番の結果を受けとりました。
3番の結果というのは、(jsonデータ見ながら)このJSONオブジェクト、一式もろともってことです。

あとは都市名を表示させるには
jsonObjの、getStringで、キーの文字列がnameってことなので、
ktに戻って、

// JSONオブジェクトの、都市名のキーを取得。⇒tvに代入して表示
val cityName とかにしてあげて、先ほど生成した、jsonObjの中の
getStringで、キーの文字列は”name”。

これをテキストビューに表示させればいいので
tvCityName.text =cityName

これでいったん、エミュレータを起動してtvCityNameに、東京都っていうのが
表示されているはずなので確認してみましょう。

    private fun weatherJsonTask(result:String){
        val tvCityName: TextView = findViewById(R.id.tvCityName)
        val tvCityWeather: TextView = findViewById(R.id.tvCityWeather)
        val tvMax: TextView = findViewById(R.id.tvMax)
        val tvMin: TextView = findViewById(R.id.tvMin)

        // まずは「3」で取得した、JSONオブジェクト一式を生成。
        val jsonObj =JSONObject(result)

        // JSONオブジェクトの、都市名のキーを取得。⇒tvに代入して表示
        val cityName =jsonObj.getString("name")
        tvCityName.text =cityName

4-3)tvにお天気を表示

それでは都市名が取得できたので
同じようにして今度はこのdescriptionっていうのを指定して
お天気の説明を表示してみましょう。

今回は、指定したいオブジェクト自体が、外で配列で囲われています。
なので、手順としては
全体のJSONオブジェクト⇒その中の配列名がweather⇒その配列の0番目⇒そしてgetStringでdescription
という流れになります。

ではやっていきましょう。

まずは
// JSONオブジェクトの、天気情報JSON配列オブジェクトを取得。
しましょう。
JSONの全体はこれ(jsonObj )なので、これの中の配列、っていう風にしていきます。

変数名を
val weatherJSONArray
とかにしてあげて
jsonObj、つまり全体のオブジェクトの中の、配列をゲットするということで
.getJSONArray()

(JSON)で配列の名前が、”weather”ということだったので、これを入れます。

(JSON)つぎに、この配列の0番目ということなので(kt)

// 現在の天気情報JSONオブジェクト(配列の0番目)を取得。
変数名を val weatherJSON  とかにしてあげて
先ほど作ったweatherJSONArray のオブジェクトをゲットするということで.getJSONObject
中は、0番目ということで、数字の0。
(JSON)これで波カッコのオブジェクトが指定できたので、あとはキーの指定、つまり
getStringで”description”ですね。

ではktに戻って
// お天気の説明(description)を取得。

変数名を、val weather とかにしてあげて、さきほど作ったweatherJSONからgetStringとして
中身を”description” 
これでこの変数に”曇りがち”っていうのが入ったので、最後の仕上げ、テキストビューに表示しましょう。

お天気はこれ(tvCityWeather)だったので、
これ(tvCityWeather)のtext にweather
tvCityWeather.text = weather

これで、テキストビューに表示また新しく表示されるようになったはずなので、
エミュレータを起動して確認してみましょう。

      // JSONオブジェクトの、天気情報JSON配列オブジェクトを取得。
        val weatherJSONArray =jsonObj.getJSONArray("weather")
        // 現在の天気情報JSONオブジェクト(配列の0番目)を取得。
        val  weatherJSON =weatherJSONArray.getJSONObject(0)
        // お天気の説明(description)を取得。
        val weather =weatherJSON.getString("description")
        // TextViewに、お天気結果を表示
        tvCityWeather.text =weather

4-4)tvに最高気温、最低気温

それでは最後に最高気温と最低気温を取得して
テキストビューに表示しましょう。で、いんたん大元のJSONデータを見てみます。

ここですね。
メインのオブジェクトの中の
テンプレチャーminが最低気温
テンプレチャーmaxが最高気温、となっています。

今回は配列じゃないので、そのままgetJSONObjectでmainを指定すればいいんですが。

気温の表示が、明らかに、いわゆる摂氏何度、の数字じゃないですよね。
これ、ケルビンだかなんだかっていう馴染みのない表示形式らしいので、
摂氏に直すには273.15を引いてあげると変換できるそうです。
なので今回は、取り出した値を計算するので、最後はgetStringではなくて
getIntで数字を出してあげる、というのがポイントです。

********メモ********************
℃表示
https://buralog.jp/python-openweathermap-get-weather/
[“temp_max”] – 273.15,”℃”
ケルビンを摂氏に変換するのは簡単です:273.15を引きます
****************************

ではktに戻ってください。
まずは、JSONオブジェクトのmainを取得するので

//JSONオブジェクトの、mainオブジェクトを取得

val main としてあげて、 jsonObj.getJSONObject()で、中身を”main”
val main = jsonObj.getJSONObject(“main”)

それではテキストビューに表示するわけですが、
//tvMaxに最高気温を表示

まずは最高気温を表示してみましょう。

tvMax.text = “”
としてあげて、まずはmainの中のキー、つまりtemp_maxを取得します。

main.getInt(“temp_max”)
で、先ほど説明のとおり、これをそのまま表示すると283度、みたいなのが表示されるので
273.15をマイナスしてあげたいわけですが。
こっちはイント型、つまり整数なので、整数と整数をあわせるということで、
今回は話を単純にしたいので少数以下は割愛して

273をマイナス、ということでこうかいてあげて
main.getInt(“temp_max”)-273
あとは波カッコにダラーマーク
${main.getInt(“temp_max”)-273}

ちょっと前後に言葉があった方が見た目がいいので
最高気温:  後ろに ℃(ドシー)
これで、この中が10だったら、最高気温10度、とかって表示されるようになりました。

あとは、最低気温はこれと全く同じなので
//tvMinに最低気温を表示”

これをそっくりコピーして、(はりつけて)
変数をMinにかえて、取得するキーも、JSONをみてお分かりのとおり temp_min にしてあげる。
最高気温 を 最低気温 になおしてあげると。

これで、東京のデータで、今回欲しい情報は全て表示されるようになったはずなので
エミュレータを起動して確認してみましょう。

あとは、最後の仕上げ。
沖縄ボタンも取得して、
クリアボタンで元に戻せば完成です。

【参考サイト】

Make a Weather App for Android | Android Studio | Kotlin

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

    コメントを残す