iOSエンジニアが悩んだAndroidのUpボタンについて

※この記事は[potatotips45](https://speakerdeck.com/endoumari/2017-potatotips-45-up-button)の発表をまとめたものです。

# はじめに
ずっとiOSアプリの開発をしていましたが、ここ最近Androidのアプリの開発をはじめました。
iOSアプリとAndroidアプリでは仕組みや実装方法は違いますが、似たような機能や動きをするのでiOSで培った経験や考え方を活かしながら実装しています。

しかし最近、iOSアプリにはない`Upボタン`の実装についてとても悩みました。
今回はその`Upボタン`について調べたことや実装方法についての紹介をしたいと思います。

# AndroidのUpボタンとは？
UpボタンはToolbarの左側にあるボタンです。
<img width="234" alt="スクリーンショット 2017-12-03 0.44.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/69901/f2043a3a-a90f-2191-90a8-1e40f24b4a0d.png">


UpボタンはAndroidのナビゲーションに関するものです。
iOSでいうところのNavigationBarのBackボタンやエッジスワイプにあたる機能になります。

AndroidのナビゲーションはUpボタンの他にBackボタンも存在します。

<img width="447" alt="スクリーンショット 2017-12-03 0.52.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/69901/0e9ab493-857d-e06d-cd9d-b3d35ee5c968.png">

BackボタンはiOSのNavigationBarのBackボタン、エッジスワイプと同じで前の画面に戻るナビゲーションです。

Upボタンは、Backボタンとは違いただ前に戻るだけのナビゲーションではありません。
Upボタンは`階層的な画面遷移`をするためのナビゲーションです。

## 階層的な画面遷移について
階層的な画面遷移とは、どこから来たかによって戻る画面が変わることです。

以下はGoogleのPlay Storeアプリの遷移例です。


**1** ホームからの遷移

以下の用にアプリのホーム画面からアプリ詳細画面に遷移した際、Upボタンを押すとホーム画面に戻ります。

<img width="670" alt="スクリーンショット 2017-12-03 1.36.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/69901/6166e440-33c0-9f3a-7094-fcb9fd5748e2.png">


**2** リストを挟んでの遷移

**1**とは違い、アプリのホーム画面からリスト画面（アプリの新着リストなど）を一旦挟みアプリ詳細に遷移するとリスト画面に戻ります。

<img width="864" alt="スクリーンショット 2017-12-03 1.37.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/69901/95702d35-7fa8-5ec8-76ed-33035f9568aa.png">


# 実装について
Upボタンの実装については、[Android Developer](https://developer.android.com/training/implementing-navigation/ancestral.html)に実装についての参考があります。

以下がサンプルコードです。

``` xml
<application ... >
    ...
    <activity
        android:name="com.example.myfirstapp.MainActivity" ...>
        ...
    </activity>
    <activity
        android:name="com.example.myfirstapp.DisplayMessageActivity"
        android:label="@string/title_activity_display_message"
        android:parentActivityName="com.example.myfirstapp.MainActivity" >
        <!-- Parent activity meta-data to support 4.0 and lower -->
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.example.myfirstapp.MainActivity" />
    </activity>
</application>
```

``` kotlin

override fun onOptionsItemSelected(item: MenuItem?): Boolean {
    when (item?.itemId) {
        android.R.id.home -> {
            val upIntent = NavUtils.getParentActivityIntent(this)
            if (NavUtils.shouldUpRecreateTask(this, upIntent!!)) {
                // 戻る先のタスクがないので生成する
                TaskStackBuilder.create(this)
                        .addNextIntentWithParentStack(upIntent)
                        .startActivities()
            } else {
                NavUtils.navigateUpTo(this, upIntent)
            }
            return true
        }
    }
    return super.onOptionsItemSelected(item)
}
```

`AndroidManifest.xml`に`PARENT_ACTIVITY`を設定しています。
Upボタンを押して画面を戻るときに、`NavUtils.getParentActivityIntent(this)`で指定したPARENT_ACTIVITYを取得して画面遷移をしています。

実装としてはすごくシンプルだと思います。
しかし、この実装方法では`PARENT_ACTIVITY`が固定されてしまうので戻る画面が静的になってしまいます。


## 戻る画面が動的に変わる実装方法

Play Storeのような動的に戻る画面遷移を実現するために、以下のように実装しました。

**実装方法**

- 戻りたい画面の`Intent`を生成する
- navigateUpToを使用する
- `Intent`にFLAG_ACTIVITY_CLEAR_TOPを設定する

**コード**

``` kotlin
class ChildActivity : AppCompatActivity(), ItemDetailEventHandler {

    private lateinit var fromActivity: FromActivity
    private const val FROM_ACTIVITY = "from_activity"

    enum class FromActivity {
        HOME,
        SEARCH_RESULT,
        ・・・
    }

    companion object {
        private const val FROM_ACTIVITY = "from_activity"

        fun start(context: Context, fromActivity: FromActivity) {
            context.startActivity(
                    Intent(context, ChildActivity::class.java).apply {
                        putExtra(FROM_ACTIVITY, fromActivity)
                    })
        }
    }

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

        fromActivity = intent.getSerializableExtra(FROM_ACTIVITY) as FromActivity 
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        when (item?.itemId) {
            android.R.id.home -> {
                val intent = createIntent(fromActivity)
                intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
                navigateUpTo(intent)

                return true
            }
            else -> return false
        }
    }

    private fun createIntent(fromActivity: FromActivity): Intent {
        return when (fromActivity) {
            FromActivity.HOME -> Intent(this, HomeActivity::class.java)
            FromActivity.SEARCH_RESULT -> Intent(this, SearchItemResultActivity::class.java)
            ・・・
        }
    }
}
```

``` kotlin
class HomeActivity : AppCompatActivity() {

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

        ChildActivity.start(context, ItemDetailActivity.FromActivity.ITEM_RANKING)
    }
}
```

戻る画面が動的になるため、`AndroidManifest.xml`に`PARENT_ACTIVITY`を設定せず、`Intent`を生成するようにしました。
また、普段は画面遷移をする際は`startActivity`を使用しますが、今回は`navigateUpTo`を使用します。
`navigateUpTo`は`Intent`で指定されたアクティビティに移動し、アクティビティを閉じるメソッドです。

動的に戻る画面遷移を実装する上で一番重要なのは`Intent`の`flags`に`FLAG_ACTIVITY_CLEAR_TOP`を設定することです。
`FLAG_ACTIVITY_CLEAR_TOP`を設定をすることで、画面が閉じられるような見た目になります。
これを設定しないで実行すると、新しく画面が作られたような見た目になってしまいます。


# まとめ

- UpボタンはAndroidのナビゲーションに関するボタン
- 同じナビゲーションに関するボタンのBackボタンとは違い、階層的な画面遷移をする
- 戻る画面が変わる画面遷移をするには`Intent`を自前で生成する必要がある

以上がUpボタンについてのまとめです。
階層的な画面遷移を実装して、よりよいアプリにしていきたいですね！


# 参考資料
[Back ボタンと Up ボタンを使用したナビゲーション](https://developer.android.com/design/patterns/navigation.html?hl=ja)