WEBに転がっているJSONデータをKotlin内で配列やリストに入れたい
(okhttp3で取得したJSONデータをKotlinのdata classでList化するよメモ)
準備
レイアウトの記述を簡易にするためViewBindingを使用
デバイス(またはシミュレータ)のインターネット環境が整っていない、permissionが設定されていないと以下のエラーが出るか後述のonFailure()に入ってしまう。
java.io.IOException: canceled due to java.lang.SecurityException: Permission denied (missing INTERNET permission?)
HTTP通信許可
<uses-permission android:name="android.permission.INTERNET" />
<!-- 今回は必要ないがhttpsのsがない通信をする場合に許可がいる -->
<application
android:usesCleartextTraffic="true"
tools:targetApi="31">
パッケージimport
※最新 Ladybug Feature Drop | 2024.2.2版
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.serialization)apply false
}
plugins {
//省略
alias(libs.plugins.kotlin.serialization)//追記
}
//省略
dependencies {
implementation(libs.http3)
implementation(libs.json)
implementation(libs.gson)
}
[versions]
com = "1.6.20"
http = "4.9.1"
ser = "1.3.2"
convert = "2.3.0"
[libraries]
http3 = {group = "com.squareup.okhttp3",name = "okhttp",version.ref ="http"}
json = {group = "org.jetbrains.kotlinx",name = "kotlinx-serialization-json",version.ref = "ser"}
gson = {group = "com.squareup.retrofit2",name = "converter-gson",version.ref = "convert"}
[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "com" }
JSONデータ
気象庁が提供しているデータを使わせていただきます。
熊谷地方気象台
https://www.jma.go.jp/bosai/forecast/data/overview_forecast/110000.json
{"publishingOffice":"熊谷地方気象台",
"reportDatetime":"2024-00-00T00:00:00+00:00",#←リアルタイムな日付
"targetArea":"埼玉県",
"headlineText":"",
"text":" 日本付近は冬型の気圧配置となっています。\n\n
埼玉県は、曇りや晴れで、雨の降っている所があります。\n\n
18日は、引き続き冬型の気圧配置となる見込みです。
このため、南部は曇り夕方から晴れで昼前雨の降る所があるでしょう。北部と秩父地方は晴れる見込みです。\n\n
19日は、冬型の気圧配置は次第に緩み、高気圧が日本海に移動するでしょう。
このため、晴れ夜曇りとなる見込みです。"}
DataClassの準備
JSONのカラムをそのままに設定
data class Weather(
val publishingOffice:String,
val reportDatetime:Date,
val targetArea:String,
val headlineText:String,
val text:String
)
OkHttpリクエスト
まずはGson.fromJson() List<T>は一旦忘れて
リクエストがきちんと通るか見てみよう。
※メインスレッドでは通信ができない。
OkHttpリクエスト時は強制的にバックグラウンドで非同期通信を行っていることは覚えておこう。
enqueueを利用しCallbackで同期っぽくなっているはずだ。
Get通信
方法はGETかPOSTとなる。
初心者はGETが無難。
焦らずOkHttpGetクラスを作成しよう。
成功でも失敗でもCallbackが終えた直後にHandlerがメインスレッドに返してくれるので安心だ。
import android.os.Handler
import android.os.Looper
import android.util.Log
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
import java.util.concurrent.TimeUnit
class OkHttpGet {
//メインスレッドへ結果を返す有り難い装置
private val mainHandler: Handler
get() = Handler(Looper.getMainLooper())
private val connectionTimeOut = "10000"
private val readTimeOut = "10000"
private val client = OkHttpClient.Builder()
.connectTimeout(connectionTimeOut.toLong(), TimeUnit.MILLISECONDS)
.readTimeout(readTimeOut.toLong(), TimeUnit.MILLISECONDS)
.build()
fun getRequest(endProcess:(String) -> Unit){
val request = Request.Builder()
.url("https://www.jma.go.jp/bosai/forecast/data/overview_forecast/110000.json")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body?.string().orEmpty()
Log.e("OK", responseBody)
mainHandler.post {
endProcess(responseBody)
}
}
override fun onFailure(call: Call, e: IOException) {
Log.e("Error", e.toString())
mainHandler.post{
endProcess(e.toString())
}
}
})
}
}
呼び出すメインスレッド側のMainActivity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
// Viewの簡易記述
import com.example.アプリ名.databinding.ActivityMainBinding
import okhttp3.OkHttpClient
private lateinit var binding: ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
Log.e("route⓵", "GET START")
OkHttpGet().getRequest{
Log.e("route⓸", "GET RESULT")
binding.textV.text = it
}
Log.e("route⓶", " GET END")
}
override fun onStart() {
super.onStart()
Log.e("route⓷", "onStart")
}
}
一応レイアウト
<!--ここのIDがbinding.~の記述になる-->
<TextView
android:id="@+id/text_v"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
これでTextViewへ結果が反映されているはずだ。
Logの確認をするとonCreateを抜けた後にOkHttpGet().getRequestの中へ
メインスレッドとして戻ってきている確認が取れる。
route⓵ E GET START
route⓶ E GET END
route⓷ E onStart
route⓸ E GET RESULT
仕上げ-Gson.fromJson() List<T>-
では以下を追記しList<dataclass>として仕上げよう。
import com.google.gson.reflect.TypeToken
import com.google.gson.Gson
//# ・・・中略
OkHttpGet().getRequest{
val listType = object:TypeToken<List<Weather>>() {}.type
val jsonData = Gson().fromJson<List<Weather>>("[${it}]", listType)
Log.e("結果", jsonData[0].publishingOffice)
Log.e("結果", jsonData[0].targetArea)
Log.e("結果", jsonData[0].reportDatetime.toString())
Log.e("結果", jsonData[0].text)
}
型(ここではreportDatetimeのDate型)には注意が必要。
実際のJSONは"reportDatetime":"2024-11-21T04:39:00+09:00"という文字列だ。
結果 E 熊谷地方気象台
結果 E 埼玉県
結果 E Thu Nov 21 04:39:00 GMT+09:00 2024
結果 E 伊豆諸島の東には低気圧があって東北東へ進んでいます。#...略
今回は取得したJSONを[]で囲み強制的に形式を合わせている。
おまけ
旧バージョン
plugins {
id("com.android.application") version "8.1.3" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.6.20" apply false
}
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
// ・・・
//#中略
buildFeatures {
viewBinding =true
dataBinding =true
}
}
dependencies {
// ・・・
//#中略
//http3
implementation("com.squareup.okhttp3:okhttp:4.9.1")
//JSON
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
implementation("com.squareup.retrofit2:converter-gson:2.3.0")
}