FragmentとgetViewLifecycleの話

この記事ではFragmentでLiveDataにObserverを登録するときはFragment#getViewLifecycleを使うと良いという話をします。

まず、Fragmentのおおまかなライフサイクルは次のようになっています。

ここで重要なのは、onDestroyが呼ばれることなく、複数回onCreateViewが呼ばれる可能性がある点です。

例えば、次のコードは間違っている可能性があります。

class MainFragment: Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.data.observe(this, Observer {
            ...
        })
    }
}

なぜなら、LiveDataに渡したthis(LifecycleOwner)は、自身のライフサイクルに駆動するためです。 このObserverが開放されるタイミングは、Fragment#onDestroyがコールされたタイミングになります。 しかし前述したとおり、Fragment#onDestroyがコールされずに、複数回onCreateViewがコールされる可能性があるため、前のObserverが開放されずに残ってしまいます。

前述のコードのObserverはFragment本体のLifecycleに駆動されるのではなく、FragmentのViewに駆動するため、この問題が起こります。 よって、FragmentにはView用のLifecycleが用意されています。それが、Fragment#getViewLifecycleです。

前述のコードは次のように書くことが出来ます。

class MainFragment: Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // ViewのLifecycleOwnerを渡す
        viewModel.data.observe(viewLifecycleOwner, Observer {
            ...
        })
    }
}

このように書くことで、ObserverがViewのライフサイクルに駆動するため、複数のObserverが登録される問題を回避することが出来ます!!

補足1

Observerが開放されるタイミングはonDestroyがコールされるタイミングとは別にもう1つあります。 それは、Observerへの参照がなくなったタイミングです。内部的にObserverはWeakReferenceで保持されており、参照が無くなったタイミングでGCされます。

補足2

Observerの重複登録問題はattach/detachを繰り返す場合におこります。 サンプルコードは次のようになります。

while (true) {
    delay(3000)
    supportFragmentManager.commitNow {
        if (fragment.isDetached) attach(fragment)
        else detach(fragment)
   }
}

Written by