Dagger によって Fragment に注入されるオブジェクトを Fragment.setUserVisibleHint の中で使う場合の注意

次のようなコードがあったとして、

class FooFragment : Fragment() {
    @Inject
    lateinit var bar: Bar

    override fun onAttach(context: Context) {
        AndroidSupportInjection.inject(this)
        super.onAttach(context)
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        bar.baz()
    }
}

ここで setUserVisibleHint(Boolean) のドキュメントを見てみると、

https://developer.android.com/reference/android/support/v4/app/Fragment#setuservisiblehint

Note: This method may be called outside of the fragment lifecycle. and thus has no ordering guarantees with regard to fragment lifecycle method calls.

Fragment のライフサイクルに関係なく呼び出されるということはつまり、 onAttach(Context) で初期化される前提の barsetUserVisibleHint(Boolean)の中で参照しようとする上記のコードは NG なのか。 ということで、どうしよっかって感じ。

NonNull -> Nullable

bar を Nullable にする方法が考えられるけど、本来 bar は NonNull 前提で扱いたい。 Fragment に対してコンストラクタインジェクションが使えないからしょうがなく onAttach(Context) で初期化せざるを得ないけど、 onAttach(Context) で必ず初期化されるからという絶対前提があることで NonNull で lateinit にしてたのもある。

class FooFragment : Fragment() {
    @JvmField
    @Inject
    var bar: Bar? = null

    override fun onAttach(context: Context) {
        AndroidSupportInjection.inject(this)
        super.onAttach(context)
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        bar?.baz()
    }

isInitialized

isInitialized で初期化されているか判定する方法が考えられる。

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/is-initialized.html

絶対初期化されるから!と宣言してるのに isInitialized で判定するのは矛盾している気がする。 判定処理の実装漏れの要因になりそうだけど、そもそも setUserVisibleHint(Context) を使う上では setUserVisibleHint(Context) の挙動は知っておこうぜみたいなところがあるので、やむなしって感じ。

class FooFragment : Fragment() {
    @Inject
    lateinit var bar: Bar

    override fun onAttach(context: Context) {
        AndroidSupportInjection.inject(this)
        super.onAttach(context)
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        if (this::bar.isInitialized) {
            bar.baz()
        }
    }
}

まとめ

setUserVisibleHint(Context) 以外でも bar を参照するのであれば後者を選ぶのがいいのかな。 setUserVisibleHint(Context) のために Nullable にするのは微妙だけど、どちらを選んでも微妙なところ。 isInitialized が実装された背景が気になる。