郵便番号から住所取得するアプリ作ってみた

今回作ってみたのは郵便番号から住所を取得するアプリです。
せっかくなのでViewBindingにも対応させてみました。
【参考】
[KotlinとAndroidStudioで遊んでみる](https://qiita.com/oda3104/items/4dc2ce5f9a0f4254ebda)


![Screenshot_20210519-150739_example001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/b76a4f5e-2f00-d89a-f0c6-5922fd7984aa.png)
![Screenshot_20210519-150755_example001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80258/3f8f1145-a2d2-4033-76d1-218f9bd8cf49.png)


#レイアウト
```xml: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:text="郵便番号検索"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:textSize="30sp"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="textPersonName"
        android:ems="10"
        android:id="@+id/editText"
        android:textSize="20sp"
        android:hint="郵便番号"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintHorizontal_bias="0.615"
        app:layout_constraintVertical_bias="0.1"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:text="検索"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.1"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="8dp"
        android:layout_marginRight="16dp" />

    <TextView
        android:id="@+id/hint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="端末の▼ボタンを押すとキーボードを閉じます"
        app:layout_constraintVertical_bias="0.2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="20dp"
        android:layout_marginBottom="40dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="郵便番号"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/hint"
        app:layout_constraintHorizontal_bias="0.1"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/zipText"
        android:textSize="20sp"
        app:layout_constraintTop_toBottomOf="@+id/editText"
        app:layout_constraintStart_toStartOf="@+id/editText"
        android:layout_marginTop="40dp"
        android:layout_marginLeft="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginBottom="20dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="都道府県名"
        app:layout_constraintTop_toBottomOf="@+id/zipText"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.1"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/prefecture"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="@+id/zipText"
        app:layout_constraintTop_toBottomOf="@+id/zipText"
        app:layout_constraintHorizontal_bias="0.056"
        app:layout_constraintVertical_bias="0.022"
        android:layout_marginTop="8dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="市区町村"
        app:layout_constraintTop_toBottomOf="@+id/prefecture"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintHorizontal_bias="0.1"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/city"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="@+id/prefecture"
        app:layout_constraintTop_toBottomOf="@+id/prefecture"
        android:layout_marginTop="20dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="番地以降"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/city"
        app:layout_constraintHorizontal_bias="0.1"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/address"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/city"
        app:layout_constraintStart_toStartOf="@+id/city"
        app:layout_constraintVertical_bias="0.043"
        app:layout_constraintWidth_default="wrap"
        android:layout_marginTop="16dp" />


</androidx.constraintlayout.widget.ConstraintLayout>
```

#AndroidManifest
通信させるためにnetwork_security_config.xmlを書き、AndroidManifest.xmlに記入します。

```xml:network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">zipcloud.ibsnet.co.jp</domain>
    </domain-config>
</network-security-config>
```
```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.example001">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:theme="@style/Theme.Example001">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```

#build.gradle
ViewBindingに対応させたいので、[こちら](https://qiita.com/mi_iroha/items/dbc07737e81776e05f8d)を参考に、build.gradle(app)に以下を追記します。
また、必要なライブラリを依存関係として追記します。

```gradle:build.gradle(app)


buildFeatures {
    viewBinding true
}

dependencies {

    implementation "com.squareup.okhttp3:okhttp:4.8.0"
    implementation "com.eclipsesource.minimal-json:minimal-json:0.9.5"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"
    
}

```

#ScopedAppActivity
非同期処理としてのコルーチンの記述を簡潔に書けるように、[こちら](https://101010.fun/programming/android-kotlin-coroutine.html)を参考にScopedAppActivityを作ります。

```kotlin:ScopedAppActivity.kt

open class ScopedAppActivity: AppCompatActivity(), CoroutineScope by MainScope() {
    
    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}

```

#HTTP通信
次に[こちら](https://qiita.com/jonghyo/items/bf3e4e06022eebe8e3eb)を参考に、
HTTP通信の処理を書きます。

```kotlin:Http.kt
class Http {
    fun httpGet(url: String): String? {
        val request = Request.Builder()
            .url(url)
            .build()
        val response = HttpClient.instance.newCall(request).execute()
        val body = response.body?.string()
        return body
    }
}
```

```kotlin:HttpClient.kt

object HttpClient {
    val instance = OkHttpClient()
}

```

最後にMainActivityを編集します。

```kotlin:MainActivity.kt

class MainActivity : ScopedAppActivity() {

    private lateinit var binding: ActivityMainBinding

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

        EditTextChange()

        binding.button.setOnClickListener (object : View.OnClickListener{
            override fun onClick(v: View?) {
                val zipCode = binding.editText
                val URL = "http://zipcloud.ibsnet.co.jp/api/search?zipcode=${zipCode.getText()}"
                getAddress(URL)
            }
        })
    }

    fun EditTextChange() {
        
        val getZipcode = binding.editText
        getZipcode.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                //テキスト変更後前の処理
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                // テキスト変更中の処理
            }

            override fun afterTextChanged(s: Editable?) {
                // テキスト変更後の処理
                val zipText = binding.zipText
                zipText.setText(s.toString())
            }
        })
    }

    fun getAddress(URL: String) = launch (Dispatchers.Main){
        val http = Http()
        try {
            async (Dispatchers.Default) {
                http.httpGet(URL)
            }.await().let {
                val result = Json.parse(it).asObject()
                val prefecture = binding.prefecture
                val city = binding.city
                val address = binding.address
                prefecture.setText(result.get("results").asArray()[0].asObject().get("address1").asString())
                city.setText(result.get("results").asArray()[0].asObject().get("address2").asString())
                address.setText(result.get("results").asArray()[0].asObject().get("address3").asString())
            }
        } catch (e: UnsupportedOperationException) {
            Log.d("[ERROR]", e.toString())
        }
    }
}

```