jetpack composeでcompose上にFragmentをのせる
こんにちは、tkyです。
※Qiitaにも書いてありますが、こちらにも投稿しておきます。
jetpack composeでFragmentの上にjetpack composeを乗せるパターンはよく見ますが、逆はなかったので記事にしてみました。
Fragmentからjetpack composeを扱うこともできれば、jetpack composeからFragmentを扱うこともできるので便利ですね。
jetpack compose on fragment
よく見るFragmentでjetpack composeを扱う方法です。 ComposeViewというViewが提供されているのでこれをonCreateViewに返すようにします。
class ComposeFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setContent { // この中でjetpack composeが扱えるようになります Text(text = "compose on fragment.") } } } }
fragment on jetpack compose
AndroidViewというViewを扱うことができるComposableが提供されているのでこれを使います。
Fragmentに限った話ではなく、既存の資産を一時的に使いまわしたい時などに便利かもしれません。
部分
AndroidView(factory = { context -> FrameLayout(context).apply { id = R.id.container } }, update = { val fragment = ComposeFragment.newInstance() val transaction = supportFragmentManager.beginTransaction() transaction.replace(it.id, fragment) transaction.commit() })
全体
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { JetpackcomposenavigationTheme { Surface(color = MaterialTheme.colors.background) { Scaffold( topBar = { TopAppBar(title = { Text("タイトル") }) }, content = { AndroidView(factory = { context -> FrameLayout(context).apply { id = R.id.container } }, update = { val fragment = LegacyFragment.newInstance() val transaction = supportFragmentManager.beginTransaction() transaction.replace(it.id, fragment) transaction.commit() }) } ) } } } } }
supportFragmentManagerをバケツリレーしなければならないのか?
上記サンプルコードはすべてActivityに書いていたのでComposeの中で supportFragmentManager
にアクセスできました。
通常Activityの中に全て書き切ることはしないためまともにやろうとするとこうなります。 バケツリレーの嵐でこの先の地獄が見えますね。。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { JetpackcomposenavigationTheme { Surface(color = MaterialTheme.colors.background) { MyApp(supportFragmentManager) } } } } } @Composable fun MyApp(fragmentManager: FragmentManager) { Scaffold( topBar = { TopAppBar(title = { Text("タイトル") }) }, content = { MyScreen(fragmentManager) } ) } @Composable fun MyScreen(fragmentManager: FragmentManager) { AndroidView(factory = { context -> FrameLayout(context).apply { id = R.id.container } }, update = { val fragment = ComposeFragment.newInstance() val transaction = fragmentManager.beginTransaction() transaction.replace(it.id, fragment) transaction.commit() }) }
こういった場合はDIコンテナを利用することが理想系かなと思います。
Koinで解説しようと思います。 下記サンプルとしていきなりFragmentManagerをDIしていますが、実際は直接DIしないでFragmentを操作するインターフェースとしてDIしてあげるのが良さそうです。
val activityModule = { activity: AppCompatActivity -> module { // 本来はFragmentManagerを操作するInterfaceを別で定義したほうが依存が分けられるかと思います single { activity.supportFragmentManager } } } class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) loadKoinModules(activityModule(this)) setContent { JetpackcomposenavigationTheme { Surface(color = MaterialTheme.colors.background) { MyApp() } } } } } @Composable fun MyApp() { Scaffold( topBar = { TopAppBar(title = { Text("タイトル") }) }, content = { MyScreen() } ) } @Composable fun MyScreen(fragmentManager: FragmentManager = get()) { // ↑koinのget()メソッドでDIします↑ AndroidView(factory = { context -> FrameLayout(context).apply { id = R.id.container } }, update = { val fragment = ComposeFragment.newInstance() val transaction = fragmentManager.beginTransaction() transaction.replace(it.id, fragment) transaction.commit() }) }