ScaloidでAndroidアプリ開発

はじめまして。9月からマイトでエンジニアとして働いている米澤です。

前職はスマホゲーム開発会社で、JavaやPHPでバックエンドのコードを書いていましたが、前々職時代はiOSやAndroidアプリの開発もしていました。
なので、「スマホアプリ開発、できます!」という触れ込みでマイト入りさせていただきましたが、実際にはクライアントはサーバーあっての物種。そしてそのサーバーサイドはScalaで組むのがマイト流なのです。
個人的にはObjective-CやJavaで組み慣れてきたスマホアプリですが、ならばここはと一念発起、SwiftとScalaを使ってみようじゃないか! と決断した次第です。

もちろん弊社には大宮という偉大なScala先人がおります。Scala初心者以前の私としては頼りがいしかない大先輩です。その大宮からのアドバイスを受け、今回のAndroid開発にはScaloidというライブラリーを使うこととなりました。

Scaloidとは

GitHubで公開されているAndroid開発ライブラリー、Scaloid。Androidライブラリーの薄いラッパー群で、昔で言えばWin32に対するWTLのようなものです。って相当わかりづらくマイナーな例えですが、コード量の減少や読みやすさ、いざとなったら低レベルにも手を簡単に突っ込める手軽さなど、バランスの良いライブラリーだと思います。

たとえば、WebViewをFragmentに貼り付けて使おうとするときの初期化コード。

Scala+Scaloid:

  private var webView: Option[SWebView] = None

  override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle) = {
    new SFrameLayout() {
      if (webView.isEmpty) {
        webView = Option(new SWebView())
      }
      webView.get.<<.fill.>>.here
    }
  }

Java:

  private WebView webView = null;

  @override
  public View onCreateView(LayoutInfrater inflater, ViewGroup container, Bundle savedInstanceState) {
    if (webView == null) {
      webView = new WebView(getActivity());
    }
    FrameLayout layout = new FrameLayout(getActivity());
    webView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    layout.addView(webView);
    return layout;
  }

Scaloidですとこれだけのコードで、「Fragmentの全体を覆うWebView」を初期化することができます(実際には、webViewを作った直後でWebViewClientなどの設定もしていますが)。
implicitの活躍でAndroid特有のContext値を隠蔽したりもしていますが、特にwebView.get.<<.fill.>>.hereの部分は、「読んでわかる」ことも含めて、OOP脳の私には十分にシゲキ的な書きまわしなのです。

Scalaのみでもシンプルに

あるいは、これはScaloidではなくてScalaじたいの恩恵ですが、アプリ唯一のActivityが複数Fragmentを切り替えつつ使うような実装で、各Fragmentにバックキー処理をさせる場合のコード。その中でも上述のWebViewが貼られたFragmentでは、ブラウザーとしての「戻る」が有効なときはバックキーで「戻る」を実行し、無効ならシステムデフォルト動作をすることになりますが、それもこんな感じで書けます。いわゆる「Androidの闇」のあまたあるうちの1つ、「DalvikのGCがマトモに動かないので循環参照はメモリリーク」を回避するため、そしてActivityがあるじとして振る舞うという特性のため、Scalaならではの素直なコードにはできないのですが、それでもJavaで書くよりははるかにスッキリです。

Scala:

// Activity

trait BackReceiver {
  def receiveBack(): Boolean
}

class MainActivity
  extends AppCompatActivity
  with TagUtil {

  private val backReceivers = new scala.collection.mutable.WeakHashMap[BackReceiver, String]

  //////

  def +=(receiver: BackReceiver): this.type = {
    backReceivers += ((receiver, ""))
    this
  }

  def -=(receiver: BackReceiver): this.type = {
    backReceivers -= receiver
    this
  }

  override def onBackPressed(): Unit = {
    if (!backReceivers.exists(_._1.receiveBack())) {
      super.onBackPressed()
    }
  }

// Fragment

class WebViewFragment
  extends Fragment
  with TagUtil
  with BackReceiver {

  //////

  override def onPause(): Unit = {
    super.onPause()
    getActivity.asInstanceOf[MainActivity] -= this
    webView.get.onPause()
  }

  override def onResume(): Unit = {
    super.onResume()
    getActivity.asInstanceOf[MainActivity] += this
    webView.get.onResume()
  }

  def receiveBack(): Boolean = {
    webView.fold(false)((w) => if (w.canGoBack) { w.goBack(); true } else false)
  }

Java:

// Activity

interface BackReceiver {
  boolean receiveBack();
}

class MainActivity extends AppCompatActivity {

  private final WeakHashMap<BackReceiver, String> backReceivers = new WeakHashMap<BackReceiver, String>();

  //////

  public void add(BackReceiver receiver) {
    backReceivers.put(receiver, "");
  }

  public void remove(BackReceiver receiver) {
    backReceivers.remove(receiver);
  }

  @override
  public void onBackPressed() {
    for (BackReciever receiver : backReceivers) {
      if (receiver.receiveBack()) {
        return;
      }
    }
    super.onBackPressed();
  }

// Fragment

class WebViewFragment extends Fragment implements BackReceiver {

  //////

  @override
  protected void onPause() {
    super.onPause();
    (MainActivity)(getActivity()).add(this);
    webView.onPause();
  }

  @override
  protected void onResume() {
    super.onResume();
    (MainActivity)(getActivity()).remove(this);
    webView.onResume();
  }

  @override
  public boolean receiveBack() = {
    if (webView != null && webView.canGoBack()) {
      webView.goBack();
      return true;
    } else {
      return false;
    }
  }

演算子オーバーロード的な何か

しかしふと思うに、Scalaの演算子的なメソッド名、これC++の演算子オーバーロードを彷彿とさせるというか。
もちろん、Scalaのそれは、Haskellにもある、オーバーロードとか言ったら失礼なパワフルな、それ以前に言語設計上必然的に出てくる機能だ、というのはうっすらとは理解しています。
でも、そもそもC++の演算子オーバーロードが、理不尽なまでに「素人が混乱するだろーが!」みたいな批判を食らってメインストリームから退場させられた、という歴史を観てきた年寄りとしては、なんやこの「復権」は! という感慨も強く覚えたりして。
それだけ、プログラマーの「底上げ」があったのか、マシンパワーがあがったために学習機会も実務機会も増えたのか…。

いずれにせよ、齢ほぼ50にしてのScalaとScaloidとの出会い。本当にエキサイティングな日々を頂戴しています。ときに自分の理解力のにぶさに落ち込み、時にスッキリ書けてアガりつつ、マイトのサービスと開発力の強化に参画できている日常をこれからもエンジョイさせていただきますよ!

Scalaエンジニアの求人