Upボタンの実装メモ

# はじめに
Upボタンの実装サンプルとかあまり詳しく書いてないので自分が実装したときのメモをまとめた。
正確性にあまり自信がない。

# Upボタン
ActionBarの左端にはUpボタンと呼ばれる上位画面へ遷移するための機能がある。
ナビゲーションバーのBackボタンとは機能的にかなり異なる。
具体的な違いはAndroidDevelopersサイトの図を見るのが一番わかりやすいと思う。
http://developer.android.com/design/patterns/navigation.html

# Upボタンの実装は難しい
Upボタンの実装のために、API level 16から`Activity`にいくつかのメソッドが追加され、`SupportLibrary`にも同様の機能を持つ`NavUtils`が追加された。
AndroidDevelopersサイトにもUp動作の実装例としてソースコードが記載されている。
http://developer.android.com/training/implementing-navigation/ancestral.html

しかし、ソースコード通りにUpボタンを実装してみても思ったとおりに動かないと感じることが多いかった。
この記事ではUpボタンの実装時に躓いたポイントと、その対処法をいくつか纏めることにする。

# IllegalArgumentException
Upボタンを押した時、以下の様な例外が発生することがある。

````java
    java.lang.IllegalArgumentException: Activity ACTIVITY_NAME does not have a parent activity name specified. (Did you forget to add the android.support.PARENT_ACTIVITY <meta-data>  element in your manifest?)
````

これは親アクティビティの指定漏れ。
親アクティビティの指定は`JellyBean`以降のための`android:parentActivityName`属性と、それ以前のための`<meta-data>`の2箇所にわけて記載する必要がある。

````AndroidManifest.xml
        <activity
            android:name=".ChildActivity"
            android:parentActivityName=".ParentActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".ParentActivity" />
        </activity>
````

# 親アクティビティが再生成される
おそらく、Upボタン実装の難易度を高く感じさせているのはこの挙動だと思う。
サンプルコードの通り`AndroidManifest.xml`に`PARENT_ACTIVITY`の指定をして、以下のようにUpボタンの実装を行ったとする。

````java
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
````

この状態で親Activityから子Activityへ遷移し、子ActivityでUpボタンを押下すると`Activity#finish()`して親Acitivityに戻るのではなく、全く新しい親Activityに遷移する。
このとき、`upintent`には`FLAG_ACTIVITY_CLEAR_TOP`が含まれているため、親Activityが再生成されたように見えてしまう。
これは正常な動作だが、感覚的には親→子と移動したのだからひとつ前の親に戻ってほしいと思うのが普通だ。
解決方法はいくつかあるが、親Activityの`launchMode`を`singleTop`にするのが一番簡単。
Java側のソースコードは修正する必要がない。

````AndroidManifest.xml
        <activity
            android:name=".ChildActivity"
            android:launchMode="singleTop"
            android:parentActivityName=".ParentActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".ParentActivity" />
        </activity>
````

`android:launchMode="singleTop"`を指定した場合、スタックの最上位が親Activityの場合はインスタンスが再利用され、`Activity#onCreate()`の代わりに`Activity#onNewIntent()`が呼ばれることになる。

## singleTopを避けたい場合
`singleTop`を指定できない、つまり親アクティビティが複数生成される可能性がある場合というのは、パラメータによって親Activityの内容が変わることが大半だ。
その場合、`NavUtils.navigateUpFromSameTask()`しただけではもともとパラメータが足りていない可能性が高い。
そういうときはUpボタン押下時の子Activityで`NavUtils.getParentActivityIntent()`を使って親Activityへの`Intent`を生成したあと、必要なパラメータをセットしてから`NavUtils.navigateUpTo()`する。
もちろん親は再生成されてしまうが、子同士の遷移によって親パラメータが変わる可能性もあるので仕方ない。
こういう場合は無理やり再生成を避けるよりは`upintent`のパラメータで復元できるようにしたほうが健全な気がする。

# 他のアプリから遷移した子ActivityでUpが動作しない
他のアプリのタスク上に子Activityがある場合、`NavUtils.navigateUpFromSameTask()`では親Activityに遷移しない。
他のアプリから呼ばれる可能性のあるActivityでは以下のように実装する必要がある。

````java
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                Intent upIntent = NavUtils.getParentActivityIntent(this);
                if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                    // 新しくタスクを生成する必要がある
                    TaskStackBuilder.create(this)
                            .addNextIntentWithParentStack(upIntent)
                            .startActivities();
                    finish();
                } else {
                    NavUtils.navigateUpTo(this, upIntent);
                }
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
````

# まとめ
大抵、実装よりもAndroidではUpとBackが別物ということから説明しなければならないのが面倒。
別物だけど単純に階層掘るだけのアプリなら`Activity#finish()`呼べばそれで済むような気がしなくもない。
もうちょっと楽になってほしい。
ところで、`Fragment`遷移の場合のUpってどうするんだろう。