ScalaでAndroid開発【ArrayAdapter編】

Scalaを使ってAndroid開発をしてみよう、ArrayAdapter編です。
ArrayAdapterの仕様も忘れかけてたので、そこら辺も一緒に説明していきます。

## ArrayAdapter（Java）
自作のクラスを使ったArrayAdapterは以下のように継承クラスを作る必要があります。
[ソース引用元](https://github.com/codepath/android_guides/wiki/Using-an-ArrayAdapter-with-ListView)

```java
public class UsersAdapter extends ArrayAdapter<User> {
    // View lookup cache
    private static class ViewHolder {
        TextView name;
        TextView home;
    }

    public UsersAdapter(Context context, ArrayList<User> users) {
       super(context, R.layout.item_user, users);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
       // Get the data item for this position
       User user = getItem(position);    
       // Check if an existing view is being reused, otherwise inflate the view
       ViewHolder viewHolder; // view lookup cache stored in tag
       if (convertView == null) {
          viewHolder = new ViewHolder();
          LayoutInflater inflater = LayoutInflater.from(getContext());
          convertView = inflater.inflate(R.layout.item_user, parent, false);
          viewHolder.name = (TextView) convertView.findViewById(R.id.tvName);
          viewHolder.home = (TextView) convertView.findViewById(R.id.tvHome);
          convertView.setTag(viewHolder);
       } else {
           viewHolder = (ViewHolder) convertView.getTag();
       }
       // Populate the data into the template view using the data object
       viewHolder.name.setText(user.name);
       viewHolder.home.setText(user.hometown);
       // Return the completed view to render on screen
       return convertView;
   }
}
```

## Viewの生成
まずはViewを生成している所をScalaで書き直してみましょう。
コードを見るとgetViewの引数であるconvertViewはnullの場合があるみたいですね。
ソースの引用元にも解説は載っていますがざっくり説明すると
Androidでのリストのアイテム部分となるViewは初回時は生成する必要がありますが、スクロールなどした場合Viewは使いまわされるためです。
Javaでは単純にnullチェックでif分岐しているようですが、Scalaでnullチェックなんてナンセンスなので修正してみます。

```scala
val view = Option(convertView) getOrElse LayoutInflater.from(getContext).inflate(R.layout.item_user, parent, false)
```

1行にまとまりましたね。
とりあえずconvertViewはOptionで包んでnullの場合はgetOrElseで処理するようにしちゃいます。
以降の処理ではこのviewを使っていきます。

## ViewHolder
これはソースコード見て思い出しました、そういえばそんなのもありましたねぇ・・・。
前述にある通りはViewは使いまわされるので一度findViewByIdしたのを毎回取得しなおすのはコストの無駄じゃない？って考えだったはずです。
なので初回時にViewHolderと呼ばれるクラスに格納してsetTagでView自体に紐づけして次回以降はgetTagで取得しているわけですね。
これもサクッとScalaにしちゃいましょう。

まずはViewHolderクラスを定義します。

```scala
case class ViewHolder(text1: TextView, text2: TextView)
```

case classは偉大ですね、格納用のクラス定義が楽です。
で、ViewHolderの生成部分を書きます。

```scala
val viewHolder = Option(view.getTag.asInstanceOf[ViewHolder]) getOrElse ViewHolder(view.findViewById(R.id.tvName).asInstanceOf[TextView], view.findViewById(R.id.tvHome).asInstanceOf[TextView])
```

ちょっと横に長いですがこんな感じです・・・、implicitとか使えばもう少し綺麗になるかもですね。
さっきと一緒でOptionで包んで処理しています。
Javaではクラスのキャスト時にnullだと例外発生しちゃうんで、nullチェックが面倒だった記憶があるのですが
asInstanceOfはモナドになっているみたい？なのでnullでもオッケーです。

## ArrayAdapter（Scala）
ということで以下が完成版です。

```scala
class UsersAdapter(context: Context, users: java.util.List[User]) extends ArrayAdapter[User](context, R.layout.item_user, users) {

  case class ViewHolder(text1: TextView, text2: TextView)

  override def getView(position: Int, convertView: View, parent: ViewGroup): View = {
    val view = Option(convertView) getOrElse LayoutInflater.from(getContext).inflate(R.layout.item_user, parent, false)
    val viewHolder = Option(view.getTag.asInstanceOf[ViewHolder]) getOrElse ViewHolder(view.findViewById(R.id.tvName).asInstanceOf[TextView], view.findViewById(R.id.tvHome).asInstanceOf[TextView])

    viewHolder.text1.setText(getItem(position).name)
    viewHolder.text1.setText(getItem(position).hometown)

    view.setTag(viewHolder)
    view
  }
}
```

空白行を除いてJavaが27行、Scalaが10行ぐらい

**圧倒的じゃないか**

何？見づらい？
慣れです！