FragmentFactoryを使ってコンストラクタ経由でFragmentに値を渡す

Fragmentに値を渡す場合は、Bundle経由で渡すのが常識だった。Fragmentはシステムで生成されるため、引数付きのコンストラクタが認識されなかったからだ。

しかし最近ではFragmentFactoryを使うことで、Fragmentに引数付きのコンストラクタを定義しても大丈夫になったということで、今回試してみることにした。

今回のコード

全コードはGitHubにある。

Activityにあるボタンを押すことで、カウンターをインクリメント・デクリメントして、そのカウンターの値を引数としてFragmentを生成するというサンプルだ。

例としてはあんまりよろしくないと思う。今回のサンプルコードのようなものであれば、ActivityのViewModelをFragmentから参照すれば解決するし、そうした方がコードとしてはスッキリするはずである。

ポイント

  1. FragmentFactoryを継承したクラスを用意し、依存性を解決する
  2. Activityのsuper.onCreate()を呼び出す前にsupportFragmentManager.fragmentFactoryでカスタムFactoryをセットする

重要なのは2番目だろう。super.onCreate()の後でファクトリをセットするとActivityの再生成時にFragmentのコンストラクタ呼び出しに失敗してアプリが落ちた。

class HasArgumentsFragmentFactory(val dep: () -> Int) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        if (className == HasArgumentsFragment::class.java.name) {
            return HasArgumentsFragment(dep())
        }
        return super.instantiate(classLoader, className)
    }
}

Factoryでは必要なFragmentのインスタンスを返すようにすればよい。今回はカウンタの値をFragmentに渡したいので、現在のカウンタを返すlambda式をFactoryに渡している1

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val vm: MainViewModel by viewModels { ViewModelProvider.NewInstanceFactory() }
    private val fragmentFactory = HasArgumentsFragmentFactory { vm.countLiveData.value ?: 0 }

    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = fragmentFactory
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.increment.setOnClickListener {
            vm.increment()
        }
        binding.decrement.setOnClickListener {
            vm.decrement()
        }
        vm.countLiveData.observe(this, Observer {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container_2, HasArgumentsFragment::class.java, null)
                .commit()
        })
    }
}

Activityではsuper.onCreate()を呼び出す前にFragmentFactoryをセットしている。順番を変えたらどうなるかというと、先に述べたとおり画面回転などでアプリが落ちる。

実際に使うか

これまた難しい。

例えばDagger2を使うのであれば、Fragmentはフィールドインジェクションで依存性を注入することになるだろうから、わざわざコンストラクタでやる必要があるのかと思える。

ただFragmentに関するDagger2の設定をFragmentFactoryに任せられるのはいいのかもしれない。一方でsupportFragmentManager.fragmentFactoryのセットをする必要が生じるので、これを忘れてアプリが落ちるという罠にはまりそうな気がする。

もっとも私はDaggerについては復習をしないとよく分からん状態になっているので、完全にイメージというか印象だけでものを言っているので参考にならない。実際のところはどうなんだろうか。

Daggerでinject呼び出すのを忘れるのを回避できていいのかなと思ったが、DispatchAndroidInjectorを使えば回避できるようだ。

Daggerの復習がてら、あらためてFragmentのコンストラクタインジェクションも試してみるとしよう。


  1. これなら別にFragmentのコンストラクタでやらなくても実現できるので、例としてはあまりよくない気がしている。 ↩︎

Amazonのほしいものリストを公開しています。仕事で欲しいもの、単なる趣味としてほしいもの、リフレッシュのために欲しいものなどを登録しています。 寄贈いただけると泣いて喜びます。大したお礼はできませんが、よりよい情報発信へのモチベーションに繋がりますので、ご検討いただければ幸いです。