どうもGANMA!チーム、しもむらです。
絶賛 Android × Scalaで開発中なのでそこから書きたいと思います。
入れ子の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の実装例も少なかったので少しでも参考になれば幸いです。
それではみなさん良いお年を〜〜〜〜!