「AndroidXでのバックキー制御」について、onBackPressedDispatcherの使い方や注意点補足

# はじめに
@androhiさんの記事「[AndroidXでのバックキー制御](http://androhi.hatenablog.com/entry/2019/01/31/201235)」を読んで、これは使わない手はない！と思いプロダクトに導入し、その時にハマった点や補足を皆様に共有したいと思い記事にしました。
（@androhiさんに許可を頂いてます。ありがとうございます！）

ブログの内容を掻い摘んで説明すると、
 `Activity#onBackPressed()`でやっていたバックキーの制御をAndroidXからは `onBackPressedDispatcher`が使えるようになったので、これを使ってFragmentでの実装を簡単にしよう！というやつです。

※ **2019/04/28 追記** activity:1.0.0-alpha07からコールバックの呼び出し方法が変更になりました。変更点は[こちら](https://github.com/AndroidDagashi/AndroidDagashi/issues/791)
※ **2019/04/05 追記** appcompat:1.1.0-alpha04からコールバックの設定方法が変更になりました。変更点は[こちら](https://qiita.com/tarumzu/items/d6b18447ad24b5b5bfa2/revisions/11)

# インストール
- 利用にはandroidxのappcompat:1.1.0以上、activity:1.0.0以上が必要です。まずはこれらをインストールします。
 
```gradle:app/build.gradle
dependencies {
    implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
    implementation 'androidx.activity:activity:1.0.0-alpha06'
}
```

- 2019/4/5時点の最新はappcompat:1.1.0-alpha04、activity:1.0.0-alpha06です。appcompat:1.1.0-alpha02についてはバグがありエラーが出るため使わないようにしてください。


<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">`NoSuchMethodException: addFontWeightStyle [class java.lang.String, int, boolean]`っていうエラー出ててなんだろーなーと調べたら &#39;androidx.appcompat:appcompat:1.1.0-alpha02&#39; 使うとだめっぽい。<a href="https://t.co/omgGlZfF5R">https://t.co/omgGlZfF5R</a></p>&mdash; たる (@tarumzu) <a href="https://twitter.com/tarumzu/status/1101375411718508545?ref_src=twsrc%5Etfw">2019年3月1日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# 使い方

- 今まではFragmentで制御する場合、このように書いていましたが…

```kotlin:OldActivity.kt
class OldActivity : AppCompatActivity() {
    override fun onBackPressed() {
        val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
        if (fragment != null && fragment is BackPressedListener) {
            (fragment as BackPressedListener).onBackPressed()
        } else {
            super.onBackPressed()
            finish()
        }
    }
}
```

```kotlin:BackPressedListener.kt
interface BackPressedListener {
    fun onBackPressed()
} 
```


```kotlin:OldFragment.kt
class OldFragment: Fragment(), BackPressedListener {
// 略
    override fun onBackPressed() {
        activity?.let {
            val fm = it.supportFragmentManager
            val backStackCnt = fm.backStackEntryCount

            // フラグメントの戻り先ある場合は戻る。
            if (backStackCnt > 1) {
                fm.popBackStack()
            }else {
                // 戻り先無い場は合終了する
                it.finish()
            }
        }
    }
}
```


👇 次のように制御を簡単にできます！

```kotlin:MainActivity.kt
// ActivityはandroidxのAppCompatActivityを継承
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {

// 略
```

```kotlin:SampleFragment.kt

class EndFragment : Fragment() {
    val mainActivity: MainActivity
        get() = (activity as MainActivity)
    val isOverrideBack = false

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

        mainActivity.onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback {
            override fun handleOnBackPressed(): Boolean {
                if (isOverrideBack) {
                    // NOTE: tureを返すと何もしない。なのでバックの制御を上書いたり出来る。
                    xxx()
                    return true
                }else {
                    // NOTE: falseの場合はfragmentがスタックに積まれていれば１つ戻る。
                    return false
                }
            }
        })
    }
```

以上です！とてもシンプルで最高ですね。
handleOnBackPressedでfalseを返す場合、 スタックに積まれたFlagment分すべて戻ってからActivityを終了させるといった動作になリます。
ただ、デフォルトの動作ではFragmentが載っていない、Activityのみのブランクページも表示されてしまうため、ブランクページを表示させたくない場合は別途下記のように書いてあげる必要があります。



```kotlin
        mainActivity.onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback {
            override fun handleOnBackPressed(): Boolean {
                val fm = mainActivity.supportFragmentManager
                val backStackCnt = fm.backStackEntryCount

                // フラグメントの戻り先がない場合は終了する。
                if (backStackCnt > 1) {
                    return false
                }

                mainActivity.finish()
                return true
            }
        })
```

# 最後に
現時点（2019/04/28）ではまだalphaではありますが、前述のバージョンであれば動作も安定しており、コードも非常にシンプルになるのでプロダクトで採用しても問題ないかなと思ってます。気になった方はぜひ使ってみてください！