2016年Android界隈で話題になったと個人的に思うこと　その1

qnote Advent Calendar 2016 2日目を担当いたします。
チャン・ジュン(日本人)です。
よろしくどうぞ

今回は、今年Android界隈で話題になったことを...
ということでKotlinでAndroidアプリ書くとこんな感じというのを紹介していこうと思います。

ちなみに、どんなアプリで紹介するかと言いますと、昨年のAdvent Calendarで書いたGoogle画像検索を利用して画像をグリッド表示するだけのアプリです。
→[記事はこちら](http://qiita.com/ChanJun/items/e1b5a16a19b0c681ff90)

## 何はともあれ

さて、Kotlinのプロジェクトを作るのはとても簡単です。
ただしKotlinのプラグインは必須なのでInstallしておいてください。

1. まずは普通に新しいプロジェクトを作りましょう。
	- 最初に作るアクティビティは何でも良いです。
2. プロジェクトができたら` Tools＞Kotlin＞Configure Kotlin in Project`でプロジェクトでKotlinが使えるようにしましょう。
3. Kotlinが使えるようになったら` Code＞Convert Java File to Kotlin File`でJavaファイルをKotlinファイルにコンバートしてもらいます。

**おめでとうございます！
Kotlinで書かれたAndroidアプリができました！**

## google画像検索を利用して画像をグリッド表示してみる

それでは本題の実装部分ですが、今回もまた通信関係はVolleyに任せちゃいます。

事前準備として
manifestに`<uses-permission android:name="android.permission.INTERNET"/>`
build.gradleのdependenciesに`compile 'com.android.volley:volley:1.0.0'`
を追記してください。

### 高階関数＋ラムダでかなりスッキリ書ける

まずはリクエストを作ります。
Google画像検索でhtmlファイルを取得して画像URLを抽出するのでStringRequestを使います。
なお、KotlinファイルとJavaファイルが混在する環境を再現したかったのでJavaで書いています。

```ImageRequest.java
public class ImageRequest extends StringRequest {

    private Map<String, String> mParams;
    private static final String URL = "http://www.google.co.jp/search";

    public ImageRequest(Response.Listener<String> listener, Response.ErrorListener errorListener, Map<String, String> mParams) {
        super(Method.GET, URL, listener, errorListener);
        this.mParams = mParams;
    }

    @Override
    public String getUrl() {
        return super.getUrl() + makeParameter();
    }

    private String makeParameter() {

        String param = "?tbm=isch";
        for (Map.Entry<String, String> entry : mParams.entrySet()) {
            param += "&" + entry.getKey() + "=" + entry.getValue();
        }

        return param;
    }
}
```

ということでMainActivity.ktからの呼び出しがこちら

```MainActivity.kt
fun requestImage() {
    val params = createParameter()
    val request = ImageRequest(
        Response.Listener {
            //抽出とアダプターのセット
        },
        Response.ErrorListener {
            //リクエストに失敗
        }, params)

    queue.add(request)
}
```

paramsにはキーワードとoffsetが必要になると思います。
Javaで書かれたクラスもKotlinで書かれたクラスもKotlinではインスタンスの生成に`new`は不要です。
また引数に関数を取ることができ(高階関数)、ラムダを渡せばかなり簡略化して書くことができます。
今回使用しているListenerは引数が1つのメソッド1つを実装するインターフェースなので、下記のようにメソッドの引数の記述を省略して`it`で表すことができます。

```
Response.ErrorListener {
    Toast.makeText(this@MainActivity, it.message, Toast.LENGTH_SHORT).show()
}
```

### lateinit修飾子で初期化を先延ばしできる

MainActivity全体はこんな感じです。
Kotlinでは基本的にnullが許容されていないので`val queue: RequestQueue`と書いただけでは赤線が引かれます。
そこで`lateinit`修飾子をつけることで初期化を先延ばしにできるのですが、初期化しなくてもランタイムまでエラーが出ないので取り扱いには要注意です。

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

    lateinit var queue: RequestQueue
    val imageUrlList = ArrayList<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)

        val fab = findViewById(R.id.fab) as FloatingActionButton
        fab.setOnClickListener { view -> Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show() }

        queue = Volley.newRequestQueue(applicationContext)
        requestImage()
    }

    fun requestImage() {
        val params = createParameter()
        val request = ImageRequest(
                Response.Listener {
                        val regex = "<img.+?src=\"(.+?)\".+?>"
                        val pattern = Pattern.compile(regex)
                        val matcher = pattern.matcher(it)

                        while (matcher.find()) {
                            imageUrlList.add(matcher.group(1))
                        }

                        if (imageUrlList.size < 100) {
                            requestImage()
                        }else {
                            val recyclerView = findViewById(R.id.image_grid) as RecyclerView
                            val layoutManager = GridLayoutManager(this@MainActivity, 4)
                           recyclerView.layoutManager = layoutManager
                            recyclerView.adapter = GridAdapter(this@MainActivity)
                        }
                },
                Response.ErrorListener {
                    Toast.makeText(this@MainActivity, it.message, Toast.LENGTH_SHORT).show()
                }, params)

        queue.add(request)
    }

    fun createParameter(): HashMap<String, String> {
        val params = HashMap<String, String>()
        params.put("q", "cat")

        if (imageUrlList.size > 0 && imageUrlList.size < 100) {
            params.put("start", imageUrlList.size.toString())
        }
        return params
    }
}
```

### Kotlinファイルのクラスが持っているグローバル変数をJavaで参照するには...

それではMainActivityのコードを踏まえてGridAdapterを見てみましょう。

```GridAdapter.java
public class GridAdapter extends RecyclerView.Adapter<GridViewHolder> {
    private ScrollingActivity mActivity;
    public GridAdapter(ScrollingActivity activity) {
        mActivity = activity;
    }
    @Override
    public GridViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new GridViewHolder(new NetworkImageView(mActivity));
    }

    @Override
    public void onBindViewHolder(GridViewHolder holder, int position) {
        holder.getImageView().setImageUrl(mActivity.getImageUrlList().get(position), new ImageLoader(mActivity.getQueue(), new GridImageCache()));
    }

    @Override
    public int getItemCount() {
        return mActivity.getImageUrlList().size();
    }

}
```

正直言って良いコードではありませんが...
さておき、ポイントは`mActivity.getImageUrlList()`と`mActivity.getQueue()`です。
MainActivityにはgetterの実装などありませんが自動的にgetterが生成されています。
逆にJavaでgetterを作っている変数については`recyclerView.adapter`のように取得できますし、セットする場合には`recyclerView.adapter = 〜`で代入をするような記述で済みます。

※GridImageCache、GridViewHolderクラスはテンプレート通りな感じですので割愛いたします。

## まとめ
勉強会に参加するとよく耳にしたKotlinという言語ですが、実際の業務で全てをKotlinで書いているという話はまだ聞きません。
一部コードでKotlinを利用することに対してリスクを考えている人に、混在する状態でも意識することなく実装を進めることができるということが伝われば良いなと思います。

## 最後に
その2を書くかどうかはまだ未定...