FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

[Scala, Android] Publisher, Subscriberでイベント通知を実装してみる

どうもGANMA!チーム、しもむらです。

絶賛 Android × Scalaで開発中なのでそこから書きたいと思います。

入れ子のFragment内のイベントを上位に伝搬するのってめんどう、もっとダイレクトな感じにしたい

Fragment内で発生したイベントを上位のActivityやFragmentに通知する場合、 FragmentのListener を作ったり、delegaterでイベントを伝搬させたりと、入れ子構造が深くなるほど実装が面倒で追いにくいな と思っていました。

scala.collection.mutable.Publisher Subscriber

Document

Publisher[A, This] は、A型のイベントを すべての登録済みsubscriberにpublishする。

フィルタを指定することで、subscriberに送信されるイベントの数を制限したりできるようです。

一般的にmixinして使われるとのこと

あまりサンプルがなかったんですが、こちらを参考にしました。

上記のPublisher、Subscriberを使ってViewのイベントを上位に通知してみます。 デザインパターンではObserverパターンと呼ばれる類

全体イメージ

SampleFragmentViewに設置したボタンをタップしたら、Activityにイベントを通知する Activityは通知を受け取って、トーストを表示するという感じの実装をします。

f:id:s_tomoyuki:20151228135848p:plain

実装

最上位のSampleActivity(Subscriber)

package com.sample.application

import android.os.Bundle
import android.support.v4.app.FragmentActivity
import android.util.Log
import android.widget.Toast
import com.sample.R

import scala.collection.mutable

// 通知されるイベントの型
case class MyEvent(message: String)

class SampleActivity extends FragmentActivity
with mutable.Subscriber[MyEvent, mutable.Publisher[MyEvent]] {

  var fragment: Option[SampleFragment] = None

  override def onCreate(savedInstanceState: Bundle): Unit = {
    super.onCreate(savedInstanceState)
    Log.d(getClass.getSimpleName, "##onCreate")

    setContentView(R.layout.sample_activity_layout)
    fragment = Some(new SampleFragment)

    getSupportFragmentManager.beginTransaction()
      .replace(R.id.fragment_container, fragment.get).commit()
  }

  override def onResume(): Unit = {
    super.onResume()
    Log.d(getClass.getSimpleName, "##onResume")

    // subscriberに登録
    for {
      fm <- fragment
      v <- fm.view
    } yield v.subscribe(this)
  }

  //mutable.Subscriberの実装
  override def notify(pub: mutable.Publisher[MyEvent], event: MyEvent): Unit = {
    Toast.makeText(getApplicationContext, event.message, Toast.LENGTH_LONG).show()
  }

}

SampleActivityに装着される SampleFragment

package com.sample.application

import android.os.Bundle
import android.support.v4.app.Fragment
import android.util.Log
import android.view.{LayoutInflater, View, ViewGroup}
import com.sample.R

class SampleFragment extends Fragment {

  var view: Option[SampleFragmentView] = None

  override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = {
    inflater.inflate(R.layout.sample_fragment_layout, container, false)
  }

  override def onStart(): Unit = {
    super.onStart()
    Log.d(getClass.getSimpleName, "##onStart")

    view = Some(new SampleFragmentView(getActivity))
    view.get.configure()
  }

}

SampleFragmentView(Publicser)

package com.sample.view

import android.support.v4.app.FragmentActivity
import android.view.View
import android.view.View.OnClickListener
import com.sample.R

import scala.collection.mutable

class SampleFragmentView(activity: FragmentActivity) extends mutable.Publisher[MyEvent] {

  private val btn = activity.findViewById(R.id.sample_button)

  def getRandomStr = new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).mkString

  def configure(): Unit = {
    //ボタンがクリックされたら登録されているSubscriberにMyEventが発生したことを通知します。
    btn.setOnClickListener(new OnClickListener {
      override def onClick(view: View): Unit = pub("通知したよー" + getRandomStr)
    })
  }

  def pub(message: String) = publish(MyEvent(message))

}

結果

できました。回転しても大丈夫

f:id:s_tomoyuki:20151228135930p:plain

まとめ

最後まで読んでいただいてありがとうございました。 Fragmentってコンストラクタ引数から渡せなないとか、色々と面倒なことも多いですが、 今回のようにイベント通知など含め少しでもシンプルに実装できたらなと常に思っています。

今回はAndroidでしたが、 mutable.Publisherの実装例も少なかったので少しでも参考になれば幸いです。

それではみなさん良いお年を〜〜〜〜!