入れ子のFragment内のイベントを上位に伝搬するのってめんどう、もっとダイレクトな感じにしたい
Fragment内で発生したイベントを上位のActivityやFragmentに通知する場合、
FragmentのListenerを作ったり、delegaterでイベントを伝搬させたりと、入れ子構造が深くなるほど実装が面倒で追いにくいなと思っていました。
scala.collection.mutable.Publisher Subscriber
Document
- http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.Publisher
- http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.Subscriber
Publisher[A, This] は、A型のイベントを すべての登録済みsubscriberにpublishする。
フィルタを指定することで、subscriberに送信されるイベントの数を制限したりできるようです。
一般的にmixinして使われるとのこと
あまりサンプルがなかったんですが、こちらを参考にしました。
- http://stackoverflow.com/questions/7892155/publisher-subscribe-in-scala
- http://stackoverflow.com/questions/3755453/scala-listener-observer
上記のPublisher、Subscriberを使ってViewのイベントを上位に通知してみます。
デザインパターンではObserverパターンと呼ばれる類
全体イメージ
SampleFragmentViewに設置したボタンをタップしたら、Activityにイベントを通知する
Activityは通知を受け取って、トーストを表示するという感じの実装をします。
実装
最上位の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))
}
結果
できました。回転しても大丈夫
まとめ
最後まで読んでいただいてありがとうございました。
Fragmentってコンストラクタ引数から渡せなないとか、色々と面倒なことも多いですが、
今回のようにイベント通知など含め少しでもシンプルに実装できたらなと常に思っています。
今回はAndroidでしたが、 mutable.Publisherの実装例も少なかったので少しでも参考になれば幸いです。