0
0

More than 1 year has passed since last update.

onOptionsItemSelected の簡略化

Last updated at Posted at 2021-10-30

■ 概要

onOptionsItemSelected() は、もっと簡易な呼び出しがあっても良さそうな気がするので、試しに作ってみたという話。

■ 仕様

本家でこんな感じの説明がされているので、仕様を以下のようにしてみた:

  • 呼び出し方法
    • itemId と、それに対応する処理を指定する。
  • 実行内容
    • 指定された itemId が対象だった場合は、対応する処理を実行し、問答無用で true を返し、super は呼ばない。1
    • 指定された itemId が対象でなかった場合は、super を呼び出す。

■ 例

☆ 従来の書き方

// For 1 menu item.
override fun onOptionsItemSelected(item: MenuItem): Boolean =
    when (item.itemId) {
        android.R.id.home -> {
            finish()
            true
        }
        else -> super.onOptionsItemSelected(item)
    }

// For multiple menu items.
override fun onOptionsItemSelected(item: MenuItem): Boolean =
    when (item.itemId) {
        android.R.id.home -> {
            finish()
            true
        }
        R.id.menu_foo -> {
            foo("bar")
            true
        }
        else -> super.onOptionsItemSelected(item)

☆ 簡略化した場合

おまじないは結構長いが2、本質的な部分はほぼ宣言的に書けるようになる。

// For 1 menu item.
override fun onOptionsItemSelected(item: MenuItem): Boolean = onOptionsItemSelected(item, { super.onOptionsItemSelected(item) },
    android.R.id.home, ::finish
)

// For multiple menu items.
override fun onOptionsItemSelected(item: MenuItem): Boolean = onOptionsItemSelected(item, { super.onOptionsItemSelected(item) }, mapOf(
    android.R.id.home to ::finish,
    R.id.menu_foo     to { foo("bar") }
))

java から呼ぶ場合の例:

// For multiple menu items.
public boolean onOptionsItemSelected(MenuItem item) {
    return onOptionsItemSelectedForJava(item, () -> super.onOptionsItemSelected(item), ImmutableMap.of(
        android.R.id.home, this::finish,
        R.id.menu_foo    , () -> foo("bar")
    ));
}

■ コード

Activity も Fragment も同一コードで対応できます。3

/**
 * A utility extension function for [Activity.onOptionsItemSelected] and [Fragment.onOptionsItemSelected].
 *
 * The following process will be executed:
 * - If [targetItemId] is selected, run [onSelected] and return true.
 * - If [targetItemId] is not selected, call <code>super.onOptionsItemSelected(item)</code> and return the result.
 */
fun onOptionsItemSelected(item: MenuItem, callSuper: () -> Boolean, targetItemId: Int, onSelected: Runnable): Boolean =
    when (item.itemId) {
        targetItemId -> {
            onSelected.run()
            true
        }
        else         -> callSuper()
    }

/**
 * @see [onOptionsItemSelected]
 */
fun onOptionsItemSelected(item: MenuItem, callSuper: () -> Boolean, targetItemIdAndOnSelectedMap: Map<Int, () -> Unit>): Boolean =
    targetItemIdAndOnSelectedMap[item.itemId]?.run { invoke(); true } ?: callSuper()

/**
 * @see [onOptionsItemSelected]
 */
fun onOptionsItemSelectedForJava(item: MenuItem, callSuper: () -> Boolean, targetItemIdAndOnSelectedMap: Map<Int, Runnable>): Boolean =
    targetItemIdAndOnSelectedMap[item.itemId]?.run { run(); true } ?: callSuper()

■ 考察

  • 対象の menuItem が存在した際に super を呼ばずに true を返す方式は、親クラスの item 処理を完全に上書きしてしまうかつ Fragment に item を伝播しないと割り切るということになる。しかしそもそも、同じ menuItem に対する処理を、継承や Fragment への伝播で拡散させるのは設計的に複雑すぎる気がするので、割り切りはアリな気がする。
  • super の呼び出しは caller sensitive なので、minSdk を API26 以上にできるまでは我慢ですかね。

ということで、ひとまずおしまい。


  1. 親クラスの処理も呼ばないし Fragment への伝播も行わない。 

  2. super の呼び出しは caller sensitive なので、minSdk を API26 以上にできるまでは我慢。 

  3. API26 以上なら super 呼び出しをライブラリ側に記述することが可能。その際は、普通にやるなら Activity と Fragment 用の extension になって、コードが別になるはず。(Reflection を使えば入り口だけ分けてその他のコードは共通化できそうだけど) 

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0