【AndroidX】Jetpack のNavigation で戻るボタンの処理をカスタマイズする(Backキーのイベントを拾う, OnBackPressedDispatcher, OnBackPressedCallback, ToolBarの戻るボタン)
- 公式サイト
- 実行した環境
- 参考サイト様
- 何がやりたいのか
- 注意事項
- 必要なライブラリ
- 依存ライブラリのバージョンの確認
- 戻るボタンの処理を記述する
- Navigationで一つ前の画面に戻る
- ActionBarのUpボタンの処理(おまけ)
AndroidのJetpackのNavigationコンポーネントを使って画面遷移をハンドリングしているアプリで、戻るボタンが押された場合の処理をカスタマイズする必要が出た時に、最近出た新しい方法で戻るボタンの処理を書きました。
その時のメモです。
Navigationコンポーネント内の機能というわけではないので、Navigationを使っていないアプリでもonBackPressed
メソッドをoverrideする代わりにその方法を使えます。
おまけでActionBarのToolBarでも戻る矢印のボタンを使っている場合のハンドリングについても書いています。
公式サイト
- Provide custom back navigation | Android Developers
- Activity | Android Developers
- Get started with the Navigation component | Android Developers
- NavController | Android Developers
実行した環境
- Android Studio 3.5
- com.android.tools.build:gradle:3.5.0
- Kotlin 1.3.50
- androidx.appcompat:appcompat:1.1.0-rc01
- androidx.navigation:navigation-fragment-ktx:2.2.0-alpha02
- androidx.navigation:navigation-ui-ktx:2.2.0-alpha02
参考サイト様
何がやりたいのか
JetpackのNavigation componentを使って戻るボタンの処理をNavigationに任せている場合でも、カスタマイズしたい時があります。
例えば何らかの処理を行わないでユーザーが画面を離脱しようとした時に、警告ダイアログを表示するなどが考えられます。
古くからある戻るボタンのハンドリング方法は、Activityクラスの中でonBackPressed
メソッドをoverrideして、その中に処理を記述していましたが、個々のFragmentから見ると親のActivityに処理を任せなくてはならず使い勝手がよくありませんでした。
Jetpackで登場したComponentActivity
のOnBackPressedDispatcher
を使うと、戻るボタンのイベントを受け取って処理を記述できるようになります。
Activityにコードを書かずに済むし、有効と無効の切り替えも簡単に出来ます。
onBackPressed
メソッドのオーバーライドの代わりにこれを使って処理を書いてみます。
注意事項
- AndroidX 移行済みの前提です。
- Jetpackは進化が早いので、この記事も古くなっているかも知れません。参考にされる場合はご了承ください。
- この記事では実装はKotlinのみを使用しています。Javaでの実装は扱っておりません。
必要なライブラリ
AndroidXのActivity が必要となります。
2019年4月25日にリリースされたandroidx.activity:activity:1.0.0-alpha07
で大幅にアップデートされたバックボタンのハンドリングの機能を使います。
その後も変更やメソッド削除等があるので、androidx.activity:activity:1.0.0
、androidx.activity:activity-ktx:1.0.0
の安定版以降を使うのがよいと思います。
公式サイトのAndroidXのActivityの導入ガイドはこちらです。
Activity | Android Developers
依存ライブラリのバージョンの確認
少し話がずれますが、androidx.activity:activity
のライブラリは、
androidx.appcompat:appcompat
やandroidx.navigation:navigation
からも依存されています。
そのためわざわざbuild.gradleのdependenciesに記述しなくてもandroidx.activity:activity
のパッケージを使用出来ます。
ですが、少し古いリリースのものだとまだ実装されていないかもしれないので、一応バージョンの確認をしておくといいでしょう。
依存関係の確認方法の一例として、下記のコマンドをプロジェクトのルート配下で実行すると、depends.txt
というテキストファイルが出来ます。
これを開いてバージョンを確認します。
$ ./gradlew app:dependencies > depends.txt
次の画像は手元で実行した時のスクリーンショットです。
androidx.activity
で検索すると、この例ではandroidx.activity:activity:1.1.0-alpha03 (*)
となっています。
1.0.0以降ですのでバージョンはOKです。
色々なライブラリがandroidx.activity
を参照していてバージョンが異なったとしても、Gradleの依存関係のルールでは参照されてる中で最も最新のものが全てに強制的に適用されます。(カスタマイズも可能)
Gradleの依存関係のルールについては公式サイトをご覧ください。
戻るボタンの処理を記述する
OnBackPressedDispatcherにコールバックを追加する
公式サイトより引用します。
class MyFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // This callback will only be called when MyFragment is at least Started. val callback = requireActivity().onBackPressedDispatcher.addCallback(this) { // Handle the back button event } // The callback can be enabled or disabled here or in the lambda } ... }
説明(ほぼ公式サイトより)
FragmentActivity
とAppCompatActivity
の継承元クラスであるComponentActivity
では、getOnBackPressedDispatcher()
を呼び出して取得できるOnBackPressedDispatcher
を使用して戻るボタンのハンドリングを行うことが出来ます。
OnBackPressedDispatcher
はaddCallback()
メソッドを呼び出されるとOnBackPressedCallback
オブジェクトを返してhandleOnBackPressed()
メソッド内で戻るボタンのイベントをキャッチした時の処理を書くことが出来ます。
コールバックの引数にはLifecycleOwner
を渡します。
OnBackPressedCallback
はLifecycleOwner
の状態がLifecycle.State.STARTED
になってから追加されます。
複数のコールバックを登録できます。
登録された順の逆の順序で有効なコールバックが呼び出されます。
コールバックの有効と無効を切り替える
isEnabled()
メソッドで切り替えます。デフォルトでtrue
になっています。
callback.isEnabled = false
コールバックはChain of Responsibility パターン
に従っています。
コールバックの有効と無効を切り替えることは、呼び出し順序を維持するためにも一時的な変更にすることがおすすめです。
特に複数のネストされたLifecycleOwner
にコールバックが追加されている場合は特に重要です。
OnBackPressedCallbackを削除する
OnBackPressedCallback
を完全に削除する場合は、remove()
メソッドを呼び出す必要があります。
ただし、コールバックは関連付けられているLifecycleOwner
が 破棄されると自動的に削除されるため、通常は必要ありません。
ActivityのonBackPressedメソッド
Activity
のonBackPressed
メソッドをoverrideする方法をとっている場合は、
OnBackPressedCallback
を代わりに使うのがおすすめです。
この変更を行うことができない場合は、次の規則が適用されます。
addCallback
で登録されたコールバックは、super.onBackPressed()を呼び出した時に評価される- onBackPressed()はOnBackPressedCallbackのインスタンスに関係なく常に呼び出される。
Navigationで一つ前の画面に戻る
Navigationのバックスタックを一つ遡るには、NavController.popBackStack()
メソッドを使います。
特定の画面に遷移したい場合は公式サイトをご覧ください。
requireActivity().onBackPressedDispatcher.addCallback(this@MyFragment) { // Handle the back button event ... findNavController().popBackStack() }
ActionBarのUpボタンの処理(おまけ)
NavigationUI.setupActionBarWithNavController()を使用してNavigationコンポーネントとアクションバーの挙動を結びつけると、NavigationがToolBarの領域に戻る矢印のUpボタンなど状況に応じて適切なナビゲーションを表示してくれるようになります。 (レイアウト内のfragmentタグで「app:defaultNavHost="true"」となっている前提です)
参考コード
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ... // レイアウト内のfragmentタグで「app:defaultNavHost="true"」となっている前提 setContentView(main_activity) val host: NavHostFragment = supportFragmentManager .findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return val navController = host.navController setSupportActionBar(binding.toolbar) val appBarConfiguration = AppBarConfiguration(navController.graph) setupActionBarWithNavController(navController, appBarConfiguration) } override fun onSupportNavigateUp(): Boolean = findNavController(R.id.nav_host_fragment).navigateUp() }
Upボタンのイベントを取得して処理をカスタマイズする場合は
Fragment
内でonOptionsItemSelectedメソッドをオーバーライドしてMenuItem
のidが android.R.id.home
の時にUpボタンが押された時の処理を記述します。
class MyFragment : Fragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { // Upボタンが押された時の処理 // falseを指定すると、Upボタンの処理が続行されて前の画面に戻る // trueを指定した時は自分でfindNavController().popBackStack()などを実行して前の画面に戻る false } R.id.action_share -> { // 何らかの処理 true } else -> super.onOptionsItemSelected(item) } } }