Controllerを超えたデータのやり取り(Android, iOS)
2017-11-11 16:57:55 +0900 JST
Webから一気にスマホのネイティブの話に変わるのですが、理由の一つとして開発環境が10月からスマホになりました。
元々どっちでもイケる派なのでスキル的には問題ないのですが、それでもネイティブはしばらく現場から離れていたので、感覚を取り戻すのには苦労しました。
書いててこれは便利だなというのがあったので紹介します。
Controller間の通信
私がネイティブ書いていた頃はSDKでこんな仕組みがあるなんて知らずに苦労した覚えがあります。
どういうときにこれが必要かというと、深くネストした子Controllerの情報を親Controllerが欲しい時とかですね。
あとはアプリ起動時にプッシュ通知が届いたときに、今開いているControllerで処理をしたいという時です。
AndroidやiOSにはControllerにそれぞれライフサイクルというものがあり、Controllerクラスをずっとメモリに保持しておけば良い、必要な時のそのクラスのプロパティに代入すれば良いという甘い考えは通用しません。
通信ができるようになるとどういった点で便利になるかというと、
- 値を伝番するためのインターフェース(プロトコル)をたくさん作る必要がない
- 結合度を弱め、テストしやすくなる
特にテストしやすくなるのは最近の開発の傾向としても是非ともといった感じです。
Androidの場合
Androidには昔からBroadcastという仕組みを使って通信を行うことが可能でした。しかし元々は他のアプリに対してBroadcastというインターフェースで通信をするためのものでした。
これをアプリ内のみに限定する方法があり、これを使えば簡単にActivityやFragment間通信ができます。AIDLなんて最初からなかったんや!
それを行ってくれるのが LocalBroadcastManager
です。
なんとこれSupport Libraryで提供されているので全てのAndroidバージョンで使えます。
送信側
用意するのはIntentのみです。IntentはBundleを入れる器として使います。
そのIntentをLocalBroadcastManagerでブロードキャストします。
val body = "AAA"
val intent = Intent("channel").apply { putExtra("body", body) }
LocalBroadcastManager.getInstance(applicationContext).sendBroadcastSync(intent)
まずIntentのコンストラクタにStringを渡します。これは受信側でフィルターとして使えます。
あとはこのIntentにBundleをつめて、Broadcastして終わり!
受信側
まずは受信するレシーバーを作成します。
private val broadcast = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("log", intent.getStringExtra("body"))
}
}
Intentなので受け取りやすいですね。
次にレシーバーを使って待ち受け状態にします。
基本的にはonResumeで待ち受けて、onPauseで解除すると良いでしょう。
override fun onResume() {
super.onResume()
// subscribe
LocalBroadcastManager.getInstance(context!!).registerReceiver(broadcast, IntentFilter().apply { addAction("channel") })
}
override fun onPause() {
super.onPause()
// unsubsidised
LocalBroadcastManager.getInstance(context!!).unregisterReceiver(broadcast)
}
これで送信されるたびに上のBroadcastReceiverが反応します。あとは煮るなり焼くなりお好きにどうぞ。
iOSの場合
iOSにはNotificationCenter経由で送受信ができます。よくよくドキュメント見てみると割と初期から使えてたみたいですね。
送信側
Androidと同じくあっさり送れます。
let body = "AAA"
NotificationCenter.default.post(name: Notification.Name(rawValue: "channel"), object: body)
おわり。らくちん。
受信側
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.onReceive(notification:)), name: Notification.Name(rawValue: "channel"), object: nil)
}
func onRecieve(notification: Notification) {
if let body = notification.object as? String {
print(body)
}
}
iOS9以降であればObserverを解除する必要がなくなったようです。なのでviewDidLoadで1回だけ登録しましょう。
これで送信されるたびにonRecieve関数が呼ばれるようになります。
Android、iOSともにこれをうまく使うことでロジックの分離がしやすくなり、関数の中をビジネスロジックのみにすることができます。