LoginSignup
16

More than 5 years have passed since last update.

新しく追加されたAACのLifecycleObserverを使ってAndroidのライフサイクルのコードをスッキリさせよう!

Last updated at Posted at 2018-03-20

Android Architecture Components(以下AAC)とはあんまり関係ないのですが、AACのライブラリのなかに入っているものでAACの中に設計として使う以外にも使えそうだなーと思ったLifecycleObserverを使ってコードスッキリクッキングをしようとおもいます。

LifecycleObserverについて

https://developer.android.com/reference/android/arch/lifecycle/Lifecycle.html
特に詳しいわけでもないのですが、LifecycleObserverActivityFragmentのライフサイクルで呼ばれるonCreate()などのコールバックメソッドが呼ばれたときにそれら(ActivityFragment)の実装と切り離してライフサイクルを扱えるようにしてくれたものです(たぶん)。LifecyclerOwnerと対になるというものみたいです。LifecyclerOwnerLifecycleObserverの関係はこんな感じです。

Kotlin
val myLifecyclerObserver = MyLifecycleObserver()
lifecyclerOwner.lifecycle.addObserver(myLifecyclerObserver)

典型的なオブザーバーパターンですね(ええ、たぶん)。
きっとLifecycle側がMyLifecycleObserverに各ライフサイクルイベントで通知してくれるんでしょうねー。
わかりやすいインターフェイスデザインで助かります。

何をスッキリさせたいか

これについて多分割とたくさんの人に動機があると思うんですが、ActivityFragmentのライフサイクルのコールバックって見づらいですよね。
僕個人は特にonStart()/onCreate()onStop()/onDestroy()をコード上になるべく近くに配置したいです。保守性って意味で。
でも簡単に割とバラバラになるし、ほかのメソッドと合わせて順番なんてその時の事情で様々です。
BroadcastReceiverなんかは僕が一番嫌いなコードになりやすいです。メンバー変数定義して、その変数の複数のコールバックで必要になり。
これまたよくやるんですが、unregisterしわすれたりなんてこともあってうっとうしいですね。

つらみコード?
class MainActivity: AppCompatActivity() {
    val broadCastReceiver = object : BroadcastReceiver() {
        override fun onReceive(contxt: Context?, intent: Intent?) {
            when (intent?.action) {
                BROADCAST_DEFAULT_ALBUM_CHANGED -> handleAlbumChanged()
                BROADCAST_CHANGE_TYPE_CHANGED -> handleChangeTypeChanged()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        supportFragmentManager.beginTransaction()
                .replace(R.id.fragment_container, MyFragment.newInstance(), "MyFragment")
                .commit()
    }

    override fun onStart() {
        super.onStart()
        // うっかりわすれるかも
        registerReceiver(broadCastReceiver, IntentFilter(BROADCAST_DEFAULT_ALBUM_CHANGED))
    }

    override fun onStop() {
        super.onStop()
        // うっかりわすれる事多し!(という前提 
        unregisterReceiver(broadCastReceiver)
    }
}

ミスをさそいやすい設計だし、コードは見にくくなる。嫌がらせのようなコードですよ。

BroadcastReceiverを扱うコードをLifecycleObserverでスッキリさせる

長年、僕がやりたかったことがついに?できるようになりました。長かった。
これが僕の理想形です!

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        supportFragmentManager.beginTransaction()
                .replace(R.id.fragment_container, MyFragment.newInstance(), "MyFragment")
                .commit()

        initBroadcastReceiver()

    }
    // [UPDATE] 2019年1月、一度自分のプロジェクトで使ってみて見直し、
    // 追加した拡張関数をつかったもの。これでスッキリが増しました!!
    fun initBroadcastReceiver() {
        val receiver = broadcastReceiver { _, intent ->
            when (intent?.action) {
                BROADCAST_DEFAULT_ALBUM_CHANGED -> handleAlbumChanged()
                BROADCAST_CHANGE_TYPE_CHANGED -> handleChangeTypeChanged()
            }
        }
        // register/unregisterがここに同時にかける!
        this@MainActivity.lifecycle.addObserver {
            doOnStart { registerReceiver(receiver, IntentFilter(BROADCAST_DEFAULT_ALBUM_CHANGED)) }
            doOnStop { unregisterReceiver(receiver) }
        }
    }
}

どうです?スッキリしましたね!!
register/unregisterのコードが一か所に並べられています!こんな風に書けたら誰も最初から失敗なんてしないっての!って感じですよ。

スッキリ?させるためにどうやったか?

では種明かしです。
まぁそんなに難しいことはしてないです。

DoOnEachLifecycleObserver.kt
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.OnLifecycleEvent

// [NEW] 2019年1月追加した拡張関数
fun Lifecycle.addObserver(f: DoOnEachLifecycleObserver.() -> Unit) =
    addObserver(DoOnEachLifecycleObserver().apply(f))

class DoOnEachLifecycleObserver : LifecycleObserver {
    private var onCreateRunner: DoOnEachRunner? = null
    private var onResumeRunner: DoOnEachRunner? = null
    private var onStartRunner: DoOnEachRunner? = null
    private var onPauseRunner: DoOnEachRunner? = null
    private var onStopRunner: DoOnEachRunner? = null
    private var onDestroyRunner: DoOnEachRunner? = null

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreated() = onCreateRunner?.run()
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStarted() = onStartRunner?.run()
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResumed() = onResumeRunner?.run()
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPaused() = onPauseRunner?.run()
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStopped() = onStopRunner?.run()
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroyed() = onDestroyRunner?.run()
    fun doOnCreatet(run: () -> Unit): DoOnEachLifecycleObserver {
        onCreateRunner = DoOnEachRunner(run)
        return this
    }
    fun doOnStart(run: () -> Unit): DoOnEachLifecycleObserver {
        onStartRunner = DoOnEachRunner(run)
        return this
    }
    fun doOnResume(run: () -> Unit): DoOnEachLifecycleObserver {
        onResumeRunner = DoOnEachRunner(run)
        return this
    }
    fun doOnPause(run: () -> Unit): DoOnEachLifecycleObserver {
        onPauseRunner = DoOnEachRunner(run)
        return this
    }
    fun doOnStop(run: () -> Unit): DoOnEachLifecycleObserver {
        onStopRunner = DoOnEachRunner(run)
        return this
    }
    fun doOnDestroy(run: () -> Unit): DoOnEachLifecycleObserver {
        onDestroyRunner = DoOnEachRunner(run)
        return this
    }
    inner class DoOnEachRunner(private val runner: () -> Unit) {
        fun run() = runner.invoke()
    }
}

このクラスを用意してあげましょう。
同じ要領でBroadcastReceiverもつけちゃいましょう!(2019年1月追記)

// [NEW] 2019年1月追加したBroadcastReceiverを手早く作るやつ
fun broadcastReceiver(f: DoOnReceiveBroadcastReceiver.(Context?, Intent?) -> Unit): BroadcastReceiver =
    DoOnReceiveBroadcastReceiver().apply {
        doOnReceive { context, intent -> this.f(context, intent) }
    }

class DoOnReceiveBroadcastReceiver : BroadcastReceiver() {
    private var onReceiveRunner: DoOnReceiveRunner? = null
    override fun onReceive(context: Context?, intent: Intent?) {
        onReceiveRunner?.run(context, intent)
    }

    inner class DoOnReceiveRunner(private val runner: (Context?, Intent?) -> Unit) {
        fun run(ctx: Context?, intent: Intent?) = runner.invoke(ctx, intent)
    }

    fun doOnReceive(run: (Context?, Intent?) -> Unit) {
        onReceiveRunner = DoOnReceiveRunner(run)
    }
}

これもあると良い感じです。

たったこれだけです。

おまけ

RxJavaを使っている人のために簡単なおまけも用意しておきました。意外と便利なんじゃないですかね?

SimpleDisposableRxLifecycleObserver.kt
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.OnLifecycleEvent
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable

class SimpleDisposableRxLifecycleObserver : LifecycleObserver {
    private val disposables: CompositeDisposable = CompositeDisposable()
    fun addAll(vararg disposable: Disposable) = disposables.addAll(*disposable)
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStopped() =  { if (!disposables.isDisposed) disposables.dispose() }
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroyed() =  { if (!disposables.isDisposed) disposables.dispose() }
}

これを使うとこんな感じに書けます。

lifecycle.addObserver(SimpleDisposableRxLifecycleObserver ().apply {
    this.addAll(
            // 説明不要な一時変数は極力作らない
            outputs.duration.subscribe(img_pulse.pulse()),
            outputs.bpmText.subscribe(text_bpm.text())
    )
})       

そしてActivityFragment側でdispose()を呼ばなくてよくなります。まぁ普段からやっていることをやらなくなるのは気持ち悪いって人は無理でしょうが、なるべくコードをきれいにしたい人には良いかもしれませんね。

まとめ

さてどうだったでしょうか?
LifecycleObserverActivityFragmentからライフサイクルの処理を一部切り出すものだということがわかりました。
AACでアーキテクチャを構成するためだけじゃなくて、それ以外にも使えそうだということがわかりました。
これを使うと今まで面倒だったコードが少しはマシになる可能性も出てきました。
ですが、やはりこのコードは今までのコードの書き方とは異なるので気持ち悪いかもしれません。
しかし既存のコードの書き方が常に良いとは限らないと思うので、何が良いか常に考えていければよいかもしれませんね。
今日はスッキリしないコードに対する一つの提案をしてみました。

ここまで読んでくださりありがとうございました。

個人的な感想を言うと、もっとLifecycleObserverの実装についていろいろ知りたいです。
書いていて、あー実際の挙動についてもっと知っておかないとだめだなーと思いました。
おいおい調査して内容に補完ができればと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16