【Android】Fragment切り替えの時にアプリバーを更新する

# 概要
[FragmentTransaction](https://developer.android.com/reference/kotlin/androidx/fragment/app/FragmentTransaction)を`replace()`から`add()`に変更した際、Fragment遷移時に更新していたアプリバーのタイトルやメニューボタン等が更新されなくなった。
`add()`の場合でもアプリバーを更新できる方法をまとめる。この記事では`onBackStackChangedListener`を使っている。

# Fragment遷移時にアプリバーの要素を更新する
```MainActivity.kt
class MainActivity : AppCompatActivity() {
    val activeFragment: BaseFragment?
        get() {
            val fragment = supportFragmentManager.findFragmentByTag("baseFragment")
            return fragment as? BaseFragment
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ... (略) ...
        // BackStackが変更された時
        supportFragmentManager.addOnBackStackChangedListener {
            activeFragment?.updateActionBar()
        }
    }
}
```

```BaseFragment.kt
abstract class BaseFragment : Fragment() {
    ... (略) ...
    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        updateActionBar()
        return null
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
    }

    open fun updateActionBar() {
        appActivity?.supportActionBar?.apply {
            setTitle("")
             // 戻るボタンを表示するかどうか
            setDisplayHomeAsUpEnabled(showsBackBarButton)
            setBackgroundDrawable(ColorDrawable(Color.parseColor("#008577")))
        }
    }
}
```

BackStackが変更された時に、Fragment遷移後にアプリバーを更新する（`activeFragment`は遷移後のFragmentになる）。
各Fragmentは`BaseFragment`を継承して作成し、`updateActionBar()`をオーバーライドして、Fragmentごとのタイトルやメニュー等をセットする。

```HomeFragment.kt
class HomeFragment : BaseFragment() {
    ... (略) ...
    override fun updateActionBar() {
        super.updateActionBar()
        this.setHasOptionsMenu(true)
        appActivity?.supportActionBar?.title = "ホーム"
    }
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.home_menu, menu)
    }
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_next -> {
                // SampleFragmentへ遷移するコード
                true
            }
            else -> {
                super.onOptionsItemSelected(item)
            }
        }
    }
}
```

```SampleFragment.kt
class SampleFragment : BaseFragment() {
    ... (略) ...
    override fun updateActionBar() {
        super.updateActionBar()
        this.setHasOptionsMenu(true)
        appActivity?.supportActionBar?.apply {
            setTitle("サンプル")
            setBackgroundDrawable(ColorDrawable(Color.RED))
        }
    }
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.sample_menu, menu)
    }
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_sample -> {
                // icon pressed
                true
            }
            else -> {
                super.onOptionsItemSelected(item)
            }
        }
    }
}
```

![update_actionbar_home.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/f82a56aa-eae1-9b91-2e89-13df6c8a0264.png)　![update_actionbar_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/5c574817-17ec-3ac7-14c3-f03b5bf63952.png)
これで、Fragment遷移の時にアプリバーのタイトルやメニューなどの要素が更新されるようになっ……あれ？

![update_actionbar_sample_increase_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/815eea63-ec94-f8cb-1ff9-ec8b3b43dfcf.png)
![update_actionbar_sample_increase_icon_line (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/e4a27691-44cb-e987-f809-d9800e3acfba.png)
お分かりいただけただろうか……

しかもこの「次へ」ボタンも押せるため、SampleFragmentとそのメニューアイコンが無限に増える。<font color="#BEBEBE">無限ループって怖くね？</font>
![update_actionbar_sample_increase_icon_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/900db765-7c74-8478-cab2-6b6dab8503ed.png)　　　![update_actionbar_sample_increase_icon_infinite.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/2716c55e-c480-0b6a-0fd7-699eee1deeb2.png)


## 表示しているFragmentのメニューのみを表示する
SampleFragmentに遷移したにもかかわらず、HomeFragmentのメニューが表示されたままになってしまっている。
そこで、現在表示しているFragmentのメニューのみを表示したい。

画面遷移をする時に、`this.setHasOptionsMenu(false)`で遷移前のFragmentのメニューを隠すことにする。

```HomeFragment.kt
class HomeFragment : BaseFragment() {
    ... (略) ...
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_next -> {
                // このFragmentのメニューを隠す
                this.setHasOptionsMenu(false)
                // SampleFragmentへ遷移するコード
                true
            }
            else -> {
                super.onOptionsItemSelected(item)
            }
        }
    }
}
```

これで、違うメニューが表示されなくなった。
![update_actionbar_sample_success.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/d30e99db-13b0-2cfa-2ca0-2f2c5582dde3.png)


# まとめ
- `onBackStackChangedListener`を使って、Fragment遷移時にアプリバーの要素を更新した。
- `this.setHasOptionsMenu(false)`でFragment遷移時に遷移前のFragmentのメニューを隠した。