【Androidアプリ入門】郵便番号から住所取得アプリを作ってみた♪

今回はAndroidアプリにPython連携するには、Webapi＋kotlin使った方が簡単ですよという助言を受けて、Webapiっぽいものを理解するために郵便番号から住所取得するアプリを作ってみました。
このアプリは以下の参考の第11章非同期処理とWeb API連携のコードを参考にしています。
【参考】
[Androidアプリ開発の教科書 Kotlin対応](https://www.amazon.co.jp/%E5%9F%BA%E7%A4%8E%EF%BC%86%E5%BF%9C%E7%94%A8%E5%8A%9B%E3%82%92%E3%81%97%E3%81%A3%E3%81%8B%E3%82%8A%E8%82%B2%E6%88%90%EF%BC%81Android%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-Kotlin%E5%AF%BE%E5%BF%9C-%E3%81%AA%E3%82%93%E3%81%A1%E3%82%83%E3%81%A3%E3%81%A6%E9%96%8B%E7%99%BA%E8%80%85%E3%81%AB%E3%81%AA%E3%82%89%E3%81%AA%E3%81%84%E3%81%9F%E3%82%81%E3%81%AE%E5%AE%9F%E8%B7%B5%E3%83%8F%E3%83%B3%E3%82%BA%E3%82%AA%E3%83%B3-CodeZine-BOOKS/dp/479816044X/ref=sr_1_1?adgrpid=76633873172&gclid=Cj0KCQiA0NfvBRCVARIsAO4930m5HmFabm-WD87Yxh1fFMYRcSaFQ9OqPBEBC9nY9PdM50clyglRdowaAryqEALw_wcB&hvadid=353107932608&hvdev=c&hvlocphy=1009292&hvnetw=g&hvpos=1t1&hvqmt=e&hvrand=10509492694699011249&hvtargid=aud-759242200006%3Akwd-332941964606&hydadcr=27270_11561194&jp-ad-ap=0&keywords=android%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8&qid=1576404008&sr=8-1)
また、[郵便番号検索API＠zipcloud](http://zipcloud.ibsnet.co.jp/doc/api)を利用しています。
ちなみに、このサービスを利用すると、ブラウザ立ち上げて以下を入力すれば、その下のような情報得られます。

```
http://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060
{
	"message": null,
	"results": [
		{
			"address1": "高知県",
			"address2": "南国市",
			"address3": "蛍が丘",
			"kana1": "ｺｳﾁｹﾝ",
			"kana2": "ﾅﾝｺｸｼ",
			"kana3": "ﾎﾀﾙｶﾞｵｶ",
			"prefcode": "39",
			"zipcode": "7830060"
		}
	],
	"status": 200
}
```
ということで、今回作るアプリはそれ自身の価値はあまりないといえますが、あくまで動きを理解するために作ります。
###やったこと
・登場する変数定義
・表示／配置を決める
・非同期処理と郵便番号を渡す
###・登場する変数定義
使い方は、
①郵便番号を入力する
②保存ボタンを押す（Listに郵便番号を渡し表示）
③List表示された郵便番号をクリックする
これで、郵便番号に対応した住所が表示されます。

```strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="app_name">住所取得</string>
	<string name="bt_click">保存</string>
	<string name="post_name">郵便番号を入力してください</string>
	<string name="tv_winfo_title">郵便番号の住所詳細</string>
</resources>
```
###・表示／配置を決める
工夫した点
①表示サイズを適当に決めました
②グループ分けしてまとめたり横に並べたりして最終的に以下の配置にしました
③Listやボタンなどが入力ボードが現れると消えてしまったり、検索結果が出ると消えてしまうのでそれが消えないように配置しました
<img width="200" style="float: left;" alt="郵便番号から住所取得アプリ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/da87677a-cc93-42a1-2d0d-c32a7231339a.jpeg">

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical">
	<TextView
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:text="@string/post_name"/>
	<EditText
		android:id="@+id/etName"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:inputType="text" />
	<Button
		android:id="@+id/btClick"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="@string/bt_click" />
	<TextView
		android:id="@+id/tvOutput"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:text=""
		android:textSize="15sp" />
	<ListView
		android:id="@+id/lvCityList"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_marginTop="25dp"/>
	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_marginTop="0dp"
		android:layout_weight="0.5"
		android:orientation="vertical">
		<TextView
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginBottom="0dp"
			android:gravity="center"
			android:text="@string/tv_winfo_title"
			android:textSize="15sp"/>
		<LinearLayout
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginBottom="0dp"
			android:orientation="horizontal">
			<TextView
				android:id="@+id/tvCityName"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:layout_marginBottom="0dp"
				android:textSize="15sp"/>
			<TextView
				android:id="@+id/tvWeatherTelop"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:layout_weight="0.5"
				android:textSize="15sp"/>
		</LinearLayout>
		<ScrollView
			android:layout_width="match_parent"
			android:layout_height="wrap_content">
			<TextView
				android:id="@+id/tvWeatherDesc"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:textSize="20sp"/>
		</ScrollView>
		</LinearLayout>
</LinearLayout>
```
###・非同期処理と郵便番号を渡す
改造は以下のとおり
もとはList表示させて項目（地域）をクリックするとそこのお天気情報を表示していたが、クリックすると郵便番号から住所を取得する。
またもとは事前にListに入れた地方のみしか表示できなかったが、入力してListに渡すようにした。
以上を実施するために
①郵便番号を入力し保存ボタンを押すと入力した郵便番号をListに渡す
②Listの項目を押した後の動きは以前のままの動作である
③表示は取得したデータをそのまま表示することとした


```MainActivity.kt
package com.websarva.wings.android.asyncsample

import ...

class MainActivity : AppCompatActivity() {
		override fun onCreate(savedInstanceState: Bundle?) {
			super.onCreate(savedInstanceState)
			setContentView(R.layout.activity_main)
			//表示ボタンであるButtonオブジェクトを取得。
			val btClick = findViewById<Button>(R.id.btClick)
			//リスナクラスのインスタンスを生成。
			val listener = HelloListener()
			//表示ボタンにリスナを設定。
			btClick.setOnClickListener(listener)
		}
		/**
		 * ボタンをクリックしたときのリスナクラス。
		 */
		private inner class HelloListener : View.OnClickListener {
			override fun onClick(view: View) {
				//名前入力欄であるEditTextオブジェクトを取得。
				val input = findViewById<EditText>(R.id.etName)
				//メッセージを表示するTextViewオブジェクトを取得。
				val output = findViewById<TextView>(R.id.tvOutput)
				//入力された名前文字列を取得。
				val inputStr = input.text.toString()
				//idのR値に応じて処理を分岐。
				when(view.id) {
					//表示ボタンの場合…
					R.id.btClick -> {
						//入力された名前文字列を取得。
						val inputStr = input.text.toString()
						output.text = inputStr
						val st = inputStr //"1240004"
						//画面部品ListViewを取得
						val lvCityList = findViewById<ListView>(R.id.lvCityList)
						//SimpleAdapterで使用するMutableListオブジェクトを用意。
						val cityList: MutableList<MutableMap<String, String>> = mutableListOf()
						//都市データを格納するMutableMapオブジェクトの用意とcityListへのデータ登録。

						//val st = "1240004"
						var city = mutableMapOf("name" to "郵便番号; " + st, "id" to st)
						cityList.add(city)
						//SimpleAdapterで使用するfrom-to用変数の用意。
						val from = arrayOf("name")
						val to = intArrayOf(android.R.id.text1)
						//SimpleAdapterを生成。
						val adapter = SimpleAdapter(
							applicationContext,
							cityList,
							android.R.layout.simple_expandable_list_item_1,
							from,
							to
						)
						//ListViewにSimpleAdapterを設定。
						lvCityList.adapter = adapter
						//リストタップのリスナクラス登録。
						lvCityList.onItemClickListener = ListItemClickListener()
					}
				}
			}
		}
		/**
		 * リストがタップされたときの処理が記述されたメンバクラス。
		 */
		private inner class ListItemClickListener : AdapterView.OnItemClickListener {
			override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
				//ListViewでタップされた行の都市名と都市IDを取得。
				val item = parent.getItemAtPosition(position) as Map<String, String>
				val cityName = item["name"]
				val cityId = item["id"]
				//取得した都市名をtvCityNameに設定。
				val tvCityName = findViewById<TextView>(R.id.tvCityName)
				tvCityName.setText(cityId + "の住所: ")
				//WeatherInfoReceiverインスタンスを生成。
				val receiver = WeatherInfoReceiver()
				//WeatherInfoReceiverを実行。
				receiver.execute(cityId)
			}
		}
		/**
		 * 非同期でお天気データを取得するクラス。
		 */
		private inner class WeatherInfoReceiver() : AsyncTask<String, String, String>() {
			override fun doInBackground(vararg params: String): String {
				//可変長引数の1個目(インデックス0)を取得。これが都市ID
				val id = params[0]
				//都市IDを使って接続URL文字列を作成。
				//val urlStr = "http://weather.livedoor.com/forecast/webservice/json/v1?city=${id}"
				val urlStr = "http://zipcloud.ibsnet.co.jp/api/search?zipcode=" + id
				//URLオブジェクトを生成。
				val url = URL(urlStr)
				//URLオブジェクトからHttpURLConnectionオブジェクトを取得。
				val con = url.openConnection() as HttpURLConnection
				//http接続メソッドを設定。
				con.requestMethod = "GET"
				//接続。
				con.connect()
				//HttpURLConnectionオブジェクトからレスポンスデータを取得。天気情報が格納されている。
				val stream = con.inputStream
				//レスポンスデータであるInputStreamオブジェクトを文字列(JSON文字列)に変換。
				val result = is2String(stream)
				//HttpURLConnectionオブジェクトを解放。
				con.disconnect()
				//InputStreamオブジェクトを解放。
				stream.close()
				//JSON文字列を返す。
				return result
			}
			override fun onPostExecute(result: String) {
				val tvWeatherDesc = findViewById<TextView>(R.id.tvWeatherDesc)
				//tvWeatherTelop.text = telop
				tvWeatherDesc.text = result //desc
			}
			/**
			 * InputStreamオブジェクトを文字列に変換するメソッド。変換文字コードはUTF-8。
			 * @param stream 変換対象のInputStreamオブジェクト。
			 * @return 変換された文字列。
			 */
			private fun is2String(stream: InputStream): String {
				val sb = StringBuilder()
				val reader = BufferedReader(InputStreamReader(stream, "UTF-8"))
				var line = reader.readLine()
				while(line != null) {
					sb.append(line)
					line = reader.readLine()
				}
				reader.close()
				return sb.toString()
			}
		}
	}
```
###まとめ
・非同期処理を利用して郵便番号から住所を取得するアプリを作ってみた
・入力とボタンを配置して入力データにより、Listデータを変更出来るようにした

・これでお天気も地域を入力すれば検索できるのでやってみる
・音声入力もできるが郵便番号はそのまましばらくすると783‐0060と―が入ってしまう