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)
で初期化される前提の bar
を setUserVisibleHint(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 が実装された背景が気になる。