jetpack composeでcompose上にFragmentをのせる

jetpack composeでFragmentの上にjetpack composeを乗せるパターンはよく見ますが、逆はなかったので記事にしてみました。

Fragmentからjetpack composeを扱うこともできれば、jetpack composeからFragmentを扱うこともできるので便利ですね。

## jetpack compose on fragment

よく見るFragmentでjetpack composeを扱う方法です。
ComposeViewというViewが提供されているのでこれをonCreateViewに返すようにします。

```kotlin
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に限った話ではなく、既存の資産を一時的に使いまわしたい時などに便利かもしれません。

### 部分

```kotlin
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()
})
```

### 全体

```kotlin
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の中に全て書き切ることはしないためまともにやろうとするとこうなります。
バケツリレーの嵐でこの先の地獄が見えますね。。

```kotlin
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](https://insert-koin.io/)で解説しようと思います。
下記サンプルとしていきなりFragmentManagerをDIしていますが、実際は直接DIしないでFragmentを操作するインターフェースとしてDIしてあげるのが良さそうです。

```kotlin
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()
    })
}
```

## まとめ

- composeの上にFragment（=AndroidのView）をのせることができた
- 既存の資産を一時的に使いまわしたい時などに便利かもしれない
- jetpack composeにはDIが不可欠