DroidKaigiのアプリのSingle Activity(Fragment)でハマったところメモ

他にもSingle Activity関連はありましたが、とりあえず大きなところでいうとこれが勉強になったので書いておきます。

# セッションのリストに戻った時にスクロールの位置が戻る問題
結論から言うとFragmentのライフサイクルへの理解が足りませんでした。

# 起こった問題

セッションの詳細の画面から、セッションのリストに戻った時にスクロールの位置が戻る問題がありました。
https://github.com/DroidKaigi/conference-app-2019/issues/33

問題があった当時のコードはこれです。`savedInstanceState`がないときにcommitしています。これの何が問題なのでしょうか？

<img src="https://qiita-image-store.s3.amazonaws.com/0/27388/4b23b5d9-f1e0-9f1b-13e6-50f268a39522.png" width="200" />

https://github.com/DroidKaigi/conference-app-2019//blob/468db8acfd4912cafae3b2f745af0a225e288313/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPageFragment.kt より

```kotlin
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupBottomSheet(savedInstanceState)
```

```kotlin
    private fun setupBottomSheet(savedInstanceState: Bundle?) {
        if (savedInstanceState == null) { // savedInstanceStateがないときだけcommit
            val fragment: Fragment = when (val tab = SessionPage.pages[args.tabIndex]) {
...
                SessionPage.Favorite -> {
                    BottomSheetFavoriteSessionsFragment.newInstance()
                }
            }

            childFragmentManager
                .beginTransaction()
                .replace(R.id.sessions_sheet, fragment)
                .disallowAddToBackStack()
                .commit()
        }
```

# 原因ととりあえずの解決策

なぜかというと**Fragmentがバックスタックから戻ってきたときは`savedInstanceState`がnullになるため、replaceしてしまうためです。**

そこでこういうPRが届きました。(https://github.com/Akihiroooo/conference-app-2019/blob/07ee9c585d39c33f7872f3055dd63d89de5c7fee/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPageFragment.kt#L127 )

**childFragmentManager.findFragmentByTag(tab.title)**によりFragmentManager判定します

```kotlin
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupBottomSheet(savedInstanceState)
```

```kotlin
    private fun setupBottomSheet(savedInstanceState: Bundle?) {
        // suppress fragment replacement
        val tab = SessionPage.pages[args.tabIndex]
        if (savedInstanceState == null &&
            childFragmentManager.findFragmentByTag(tab.title) == null
        ) {
```

# 最終的な解決策

ここで自分がうまく動いた！とツイートします
そこで以下のツッコミが入ります

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">このPRはsetupBottomSheetをonCreateで呼ぶだけで修正できるのではないかと思いました。</p>&mdash; くぼぼ (@swiz_ard) <a href="https://twitter.com/swiz_ard/status/1082943682402181120?ref_src=twsrc%5Etfw">2019年1月9日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ここで以下のことを学びました。
**バックスタックから戻ってきたときはonCreateは呼ばれずonCreateViewやonActivityCreatedは呼ばれる。**
**孫フラグメントを作るときはFragmentのonCreateでcommit()をすることで、フラグメントが無駄にまた作られることを防げる。**

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27388/50156012-1de8-c3f2-f576-8996818ff3ae.png)
https://github.com/xxv/android-lifecycle より


最終的に以下のようにonCreateで実装すれば完成です！
https://github.com/DroidKaigi/conference-app-2019/blob/e714989139f182fe499b1b3d4d4a4f5e8b9cd9cc/feature/session/src/main/java/io/github/droidkaigi/confsched2019/session/ui/SessionPageFragment.kt#L91

```kotlin
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState == null) {
            setupSessionsFragment()
        }
    }
...
    private fun setupSessionsFragment() {
        val tab = SessionPage.pages[args.tabIndex]
        val fragment: Fragment = when (tab) {
...
            SessionPage.Favorite -> {
                BottomSheetFavoriteSessionsFragment.newInstance()
            }
        }

        childFragmentManager
            .beginTransaction()
            .replace(R.id.sessions_sheet, fragment, tab.title)
            .disallowAddToBackStack()
            .commit()
    }
```