AndroidからAPIを叩いてJSON取って中身を表示させるまで


みんめです。
Android WebAPI JSON .. でググると情報は出てくるものの、
私のような初心者には取っつきにくいようなものばかりを目にします。

そこで、**セキュリティだのエラーハンドリングだのなんだのをとりあえず置いておいて**、
とにかくWebAPI叩いて取得して中身をテキストに表示させるということをわかりやすくメモします。

#やってみる

環境は、
Kotlin、Android Studio 3.1　でいきます。

##Layout
Layoutはとにかくシンプルに、MainActivityのなかに、ボタンとテキストビューを一つずつ。
配置とかはどうでもいいですが、一応コード載せておきます。ConstraintLayoutで配置しました。

ボタンのidは"btn"、テキストビューのidは"textView"にしています。

```kotlin:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
```

##Activity
Activityにいきます。まず、MainActivityクラスの中身から。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn.setOnClickListener {
            //ボタンがクリックされたらAPIを叩く。
        }
    }

    //後からここにinner classを作ります。

}
```

とにかくシンプルに。ボタンが押されたらAPI叩いて、取ってきたデータをテキストビューに表示します。
inner classについてはkotlin inner classで調べればすぐにわかります。今回、MainActivityでしかAPIを叩く処理をしないのでこうしてますが、別ファイルにクラスを作ってそれを呼んできても大丈夫です。
ってか多分そっちのが普通です。

##JSON
今回はこんなJsonを返すAPIを叩くことを想定します。

```json:json.txt
{
  "movies": [
    {
      "title": "Your Name.",
      "year": 2016
    }
  ]
}
```

##非同期処理クラス

ここが今回のテーマポイントです。非同期処理とはなんぞやということは、ググればわかりやすいページがあります。
Androidでは通信処理などを非同期処理で行わないといけないルールがあるのでそれに従うだけです。
非同期処理の実装方法は色々あるんですが、とりあえずフレームワークなしでいきます。
非同期処理を行うクラスにはAsynkTaskを継承してあげます。
MainActivityの "//後からここに…" の所に以下を書いてください。

```kotlin:MainActivity.kt
inner class HitAPITask: AsynkTask<String, String, String>(){

  override fun doInBackground(vararg params: String?): String? {
      //ここでAPIを叩きます。バックグラウンドで処理する内容です。
      var connection: HttpURLConnection? = null
      var reader: BufferedReader? = null
      val buffer: StringBuffer
      
      try {
            //param[0]にはAPIのURI(String)を入れます(後ほど)。
            //AsynkTask<...>の一つめがStringな理由はURIをStringで指定するからです。
            val url = URL(params[0])
            connection = url.openConnection() as HttpURLConnection 
            connection.connect()  //ここで指定したAPIを叩いてみてます。

            //ここから叩いたAPIから帰ってきたデータを使えるよう処理していきます。

            //とりあえず取得した文字をbufferに。
            val stream = connection.inputStream
            reader = BufferedReader(InputStreamReader(stream))
            buffer = StringBuffer()
            var line: String?
            while (true) {
                line = reader.readLine()
                if (line == null) {
                    break
                }
                buffer.append(line)
                //Log.d("CHECK", buffer.toString())
            }

            //ここからは、今回はJSONなので、いわゆるJsonをParseする作業（Jsonの中の一つ一つのデータを取るような感じ）をしていきます。

            //先ほどbufferに入れた、取得した文字列
            val jsonText = buffer.toString()

            //JSONObjectを使って、まず全体のJSONObjectを取ります。
            val parentJsonObj = JSONObject(jsonText)
            //今回のJSONは配列になっているので（データは一つですが）、全体のJSONObjectから、getJSONArrayで配列"movies"を取ります。
            val parentJsonArray = parentJsonObj.getJSONArray("movies")

            //JSONArrayの中身を取ります。映画"Your Name"のデータは、配列"movies"の０番目のデータなので、
            val detailJsonObj = parentJsonArray.getJSONObject(0)  //これもJSONObjectとして取得

            //moviesの0番目のデータのtitle項目をStringで取ります。これで中身を取れました。
            val movieName: String = detailJsonObj.getString("title")  // => Your Name.
            //公開年を取りたい時も同じようにすれば良いです。
            val year: Int = detailJsonObj.getInt("year")  // => 2016

            //Stringでreturnしてあげましょう。
            return "$movieName - $year"  // => Your Name. - 2016

            //ここから下は、接続エラーとかJSONのエラーとかで失敗した時にエラーを処理する為のものです。
            } catch (e: MalformedURLException) {
                e.printStackTrace()
            } catch (e: IOException) {
                e.printStackTrace()
            } catch (e: JSONException) {
                e.printStackTrace()
            } 
            //finallyで接続を切断してあげましょう。
            finally {
                connection?.disconnect()
                try {
                    reader?.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
            //失敗した時はnullやエラーコードなどを返しましょう。
            return null
        }
    }

    //返ってきたデータをビューに反映させる処理はonPostExecuteに書きます。これはメインスレッドです。
    override fun onPostExecute(result: String?) {
        super.onPostExecute(result)
        if(result == null) return

        textView.text = result
    }
}
```

長くなりましたが、バックグラウンドでの処理は以上です。
コード中のコメントを見ながら、上から順に辿ってもらえれば、なんとなくわかると思います。
では、ボタンを押した時に、params[0]にURLを指定して非同期処理を呼び出す処理を書いていきます。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn.setOnClickListener {
            //ボタンがクリックされたらAPIを叩く。
            HitAPITask().execute("ここにURL")
        }
    }

    inner class HitAPITask: AsynkTask<String, String, String>{
        //省略
    }
}
```

また、インターネット通信を扱うのでAndroidManifest.xmlにパーミッションの記述が必要になります。

```xml:AndroidManifest.xml
<manifest
    <!-- 省略 -->
    >
    <uses-permission
        android:name="android.permission.INTERNET" />
    <application
        <!-- 省略 -->
    </application>
</manifest>
```

実際に公開されているAPIからデータを引っ張ってきたいときは、
URLと、JsonをParseする部分をうまくいじってあげれば動くと思います。


#最後に

Connection 
 　=> StringBuffer 
　　　　=> JSONObject 
　　　　　　=> (JSONArray => JSONObject) 
　　　　　　　　=>  中身。

もし間違っている部分などありましたら指摘して頂けると幸いです。