回答編集履歴

2

説明追加

2023/10/14 08:44

投稿

jimbe
jimbe

スコア13168

test CHANGED
@@ -1,3 +1,5 @@
1
+ kotlin ならコルーチンなのかもしれませんがまだそこまで分からないので出来る範囲で動作をマネてみました。
2
+ java から変換して修正したので java っぽさが残っているかもしれませんが、何かの参考になれば…
1
3
 
2
4
  MainActivity.kt
3
5
  ```kotlin

1

途中で間違って送ってしまった

2023/10/14 08:37

投稿

jimbe
jimbe

スコア13168

test CHANGED
@@ -55,4 +55,216 @@
55
55
  ```
56
56
  res/layout/activity_main.xml
57
57
  ```xml
58
+ <?xml version="1.0" encoding="utf-8"?>
59
+ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
60
+ xmlns:app="http://schemas.android.com/apk/res-auto"
61
+ xmlns:tools="http://schemas.android.com/tools"
62
+ android:layout_width="match_parent"
63
+ android:layout_height="match_parent"
64
+ tools:context=".MainActivity">
65
+
66
+ <androidx.recyclerview.widget.RecyclerView
67
+ android:id="@+id/city_list"
68
+ android:layout_width="0dp"
69
+ android:layout_height="0dp"
70
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
71
+ app:layout_constraintBottom_toTopOf="@id/fragment_container_view"
72
+ app:layout_constraintEnd_toEndOf="parent"
73
+ app:layout_constraintStart_toStartOf="parent"
74
+ app:layout_constraintTop_toTopOf="parent" />
75
+
76
+ <androidx.fragment.app.FragmentContainerView
77
+ android:id="@+id/fragment_container_view"
78
+ android:layout_width="0dp"
79
+ android:layout_height="0dp"
80
+ app:layout_constraintBottom_toBottomOf="parent"
81
+ app:layout_constraintEnd_toEndOf="parent"
82
+ app:layout_constraintStart_toStartOf="parent"
83
+ app:layout_constraintTop_toBottomOf="@id/city_list"
84
+ tools:layout="@layout/fragment_weather_info" />
85
+
86
+ </androidx.constraintlayout.widget.ConstraintLayout>
58
- ```
87
+ ```
88
+ WeatherInfoFragment.kt
89
+ ```kotlin
90
+ import android.os.Bundle
91
+ import android.view.View
92
+ import android.widget.TextView
93
+ import androidx.fragment.app.Fragment
94
+ import androidx.lifecycle.LiveData
95
+ import androidx.lifecycle.MutableLiveData
96
+ import androidx.lifecycle.ViewModel
97
+ import androidx.lifecycle.ViewModelProvider
98
+ import androidx.lifecycle.switchMap
99
+ import java.text.MessageFormat
100
+
101
+ //WeatherInfoViewModel の WeatherInfoLiveData から通知を受けて表示する.
102
+ class WeatherInfoFragment : Fragment(R.layout.fragment_weather_info) {
103
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
104
+ super.onViewCreated(view, savedInstanceState)
105
+
106
+ val viewModel = ViewModelProvider(requireActivity())[WeatherInfoViewModel::class.java]
107
+
108
+ val telopText = view.findViewById<TextView>(R.id.telop_text)
109
+ val descriptionText = view.findViewById<TextView>(R.id.description_text)
110
+
111
+ val telopFormat = getString(R.string.telop_format)
112
+ val descriptionFormat = getString(R.string.description_format)
113
+
114
+ viewModel.weatherInfoLiveData.observe(viewLifecycleOwner) {
115
+ if (it == null) {
116
+ telopText.text = ""
117
+ descriptionText.text = ""
118
+ } else {
119
+ telopText.text = MessageFormat.format(telopFormat, it.cityName)
120
+ descriptionText.text = MessageFormat.format(descriptionFormat, it.weather, it.latitude, it.longitude)
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ class WeatherInfoViewModel : ViewModel() {
127
+ lateinit var model: Model
128
+
129
+ private val _cityLiveData = MutableLiveData<City?>()
130
+
131
+ private val _weatherInfoLiveData = _cityLiveData.switchMap { model?.requestWeatherInfo(it) }
132
+ val weatherInfoLiveData: LiveData<WeatherInfo?> get() = _weatherInfoLiveData
133
+
134
+ fun requestWeatherInfo(city: City) {
135
+ _cityLiveData.value = city
136
+ }
137
+ }
138
+ ```
139
+ res/layout/fragment_weather_info.xml
140
+ ```xml
141
+ <?xml version="1.0" encoding="utf-8"?>
142
+ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
143
+ xmlns:app="http://schemas.android.com/apk/res-auto"
144
+ xmlns:tools="http://schemas.android.com/tools"
145
+ android:layout_width="match_parent"
146
+ android:layout_height="match_parent"
147
+ android:layout_margin="8dp"
148
+ tools:context=".WeatherInfoFragment">
149
+
150
+ <TextView
151
+ android:id="@+id/title_label"
152
+ android:layout_width="wrap_content"
153
+ android:layout_height="wrap_content"
154
+ android:text="@string/wetherinfo_title"
155
+ android:textSize="24sp"
156
+ app:layout_constraintEnd_toEndOf="parent"
157
+ app:layout_constraintStart_toStartOf="parent"
158
+ app:layout_constraintTop_toTopOf="parent" />
159
+
160
+ <TextView
161
+ android:id="@+id/telop_text"
162
+ android:layout_width="0dp"
163
+ android:layout_height="wrap_content"
164
+ android:layout_marginTop="8dp"
165
+ android:textAlignment="textStart"
166
+ android:textSize="20sp"
167
+ app:layout_constraintEnd_toEndOf="parent"
168
+ app:layout_constraintStart_toStartOf="parent"
169
+ app:layout_constraintTop_toBottomOf="@id/title_label"
170
+ tools:text="telop" />
171
+
172
+ <TextView
173
+ android:id="@+id/description_text"
174
+ android:layout_width="0dp"
175
+ android:layout_height="0dp"
176
+ android:layout_marginTop="8dp"
177
+ app:layout_constraintBottom_toBottomOf="parent"
178
+ app:layout_constraintEnd_toEndOf="parent"
179
+ app:layout_constraintStart_toStartOf="parent"
180
+ app:layout_constraintTop_toBottomOf="@id/telop_text"
181
+ tools:text="description" />
182
+
183
+ </androidx.constraintlayout.widget.ConstraintLayout>
184
+ ```
185
+ Model.kt
186
+ ```kotlin
187
+ import androidx.lifecycle.LiveData
188
+ import androidx.lifecycle.MutableLiveData
189
+ import org.json.JSONException
190
+ import org.json.JSONObject
191
+ import java.net.HttpURLConnection
192
+ import java.net.URL
193
+ import java.nio.charset.StandardCharsets
194
+ import java.util.concurrent.Executors
195
+
196
+ class Model {
197
+ companion object {
198
+ private const val WEATHERINFO_URL = "https://api.openweathermap.org/data/2.5/weather?lang=ja"
199
+ private const val APP_ID = "c1a8424a8556a2fa057cffa058930173"
200
+ }
201
+
202
+ private val executorService = Executors.newSingleThreadExecutor()
203
+
204
+ //サーバから天気情報を取得し LiveData に入れる.
205
+ fun requestWeatherInfo(city: City?): LiveData<WeatherInfo?> {
206
+ val liveData = MutableLiveData<WeatherInfo?>()
207
+ if (city != null) executorService.submit(WeatherInfoReceiver(URL("${WEATHERINFO_URL}&q=${city.q}&appid=${APP_ID}"), liveData))
208
+ return liveData
209
+ }
210
+ }
211
+
212
+ class WeatherInfoReceiver(private val url: URL, private val liveData: MutableLiveData<WeatherInfo?>) : Runnable {
213
+ override fun run() {
214
+ var con: HttpURLConnection? = null
215
+ try {
216
+ con = url.openConnection().apply {
217
+ connectTimeout = 1000
218
+ readTimeout = 1000
219
+ } as HttpURLConnection
220
+ liveData.postValue(parseJSON(con.inputStream.reader(StandardCharsets.UTF_8).readText()))
221
+ } catch (e: Exception) {
222
+ e.printStackTrace()
223
+ liveData.postValue(null)
224
+ } finally {
225
+ con?.disconnect()
226
+ }
227
+ }
228
+
229
+ @Throws(JSONException::class)
230
+ fun parseJSON(json: String): WeatherInfo {
231
+ val root = JSONObject(json)
232
+ val cityName = root.getString("name")
233
+
234
+ val coord = root.getJSONObject("coord")
235
+ val latitude = coord.getString("lat")
236
+ val longitude = coord.getString("lon")
237
+
238
+ val weather = root.getJSONArray("weather").getJSONObject(0).getString("description")
239
+
240
+ return WeatherInfo(cityName, latitude, longitude, weather)
241
+ }
242
+ }
243
+
244
+ class City(val name: String, val q: String) {
245
+ override fun toString() =
246
+ "City@${hashCode().toString(16)}[name=$name,q=$q]"
247
+ }
248
+
249
+ class WeatherInfo(val cityName: String, val latitude: String, val longitude: String, val weather: String) {
250
+ override fun toString() =
251
+ "WeatherInfo@${hashCode().toString(16)}[cityName=$cityName,latitude=$latitude,longitude=$longitude,weather=$weather]"
252
+ }
253
+ ```
254
+ res/values/strings.xml
255
+ ```xml
256
+ <resources>
257
+ <string name="app_name">お天気情報</string>
258
+ <string name="wetherinfo_title">お天気詳細</string>
259
+ <string name="telop_format">{0}の天気</string>
260
+ <string name="description_format">現在は{0}です。\n緯度は{1}度で経度は{2}度です。</string>
261
+ </resources>
262
+ ```
263
+ AndroidManifest.xml に追加
264
+ ```
265
+ <uses-permission android:name="android.permission.INTERNET"/>
266
+ ```
267
+ build.gradle(:app) dependencies に追加
268
+ ```
269
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
270
+ ```