Navigation ComponentのstartDestinationでもHomeAsUpボタンを表示したい

Navigation Componentを教科書通り、BasicActivityなどのサンプルのまま実装すると、NavigationGraphとActionBarがリンクして、startDestinationに指定したFragmentではActionBarのUPボタンが表示されず、遷移先ではUPボタンが表示されるという動作になります。

|FirstFragment|SecondFragment|
|--|--|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/9dc4392f-21d0-bd5f-3823-f75afd3bb85a.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/6e70643b-f045-4625-0a7f-9afa03a50b63.png)|

しかし、SingleActivityで最初から作る場合はともかく、一部画面からNavigation Componentを適用していこうとした場合など、トップのActivityでもUPボタンが欲しい場合があります。これをどうすれば良いかを調べるのにチョット苦労したのでご紹介

# 結論

AppBarConfigurationを変更すれば良いです。
サンプルでは以下のようになっていますが、

```kotlin
val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
```

以下のように変更します。

```kotlin
val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration.Builder().build()
setupActionBarWithNavController(navController, appBarConfiguration)
```

これだけです。

|FirstFragment|SecondFragment|
|--|--|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/3a896c6f-7df6-a7fc-1a09-2852cf658f1b.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/8cbafb64-d744-7043-3f05-e6c9f34f5a75.png)|

あとは、`navController.navigateUp`がNavigationGraph内でのup操作ができなくなるとfalseが返るので、以下の`super.onSupportNavigateUp()`の場所に実行したい処理を書きます。

```kotlin
override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment_content_main)
    return navController.navigateUp(appBarConfiguration)
            || super.onSupportNavigateUp()
}
```

# どういうことか

`setupActionBarWithNavController`の処理を追ってみましょう

```kotlin:NavController.kt
fun AppCompatActivity.setupActionBarWithNavController(
    navController: NavController,
    configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
) {
    NavigationUI.setupActionBarWithNavController(this, navController, configuration)
}
```

`NavigationUI.setupActionBarWithNavController`を呼び出しています。その内容は

```java:NavigationUI.java
public static void setupActionBarWithNavController(@NonNull AppCompatActivity activity,
        @NonNull NavController navController,
        @NonNull AppBarConfiguration configuration) {
    navController.addOnDestinationChangedListener(
            new ActionBarOnDestinationChangedListener(activity, configuration));
}
```

NavController に OnDestinationChangedListener を登録してますね。`ActionBarOnDestinationChangedListener`の中を見てみましょう

```java:ActionBarOnDestinationChangedListener.java
@RestrictTo(RestrictTo.Scope.LIBRARY)
class ActionBarOnDestinationChangedListener extends
        AbstractAppBarOnDestinationChangedListener {
    private final AppCompatActivity mActivity;

    ActionBarOnDestinationChangedListener(@NonNull AppCompatActivity activity,
            @NonNull AppBarConfiguration configuration) {
        super(activity.getDrawerToggleDelegate().getActionBarThemedContext(), configuration);
        mActivity = activity;
    }

    @Override
    protected void setTitle(CharSequence title) {
        ActionBar actionBar = mActivity.getSupportActionBar();
        actionBar.setTitle(title);
    }

    @Override
    protected void setNavigationIcon(Drawable icon,
            @StringRes int contentDescription) {
        ActionBar actionBar = mActivity.getSupportActionBar();
        if (icon == null) {
            actionBar.setDisplayHomeAsUpEnabled(false);
        } else {
            actionBar.setDisplayHomeAsUpEnabled(true);
            ActionBarDrawerToggle.Delegate delegate = mActivity.getDrawerToggleDelegate();
            delegate.setActionBarUpIndicator(icon, contentDescription);
        }
    }
}
```

ActionBarのタイトル変更と、今回目的のActionBarのアイコンの変更が行われていますね。まさに今回調べたかった`setDisplayHomeAsUpEnabled`を実行している箇所を見つけました。`setNavigationIcon`のiconがnullの時にUpボタンが消えるみたいですね。
親クラスの`AbstractAppBarOnDestinationChangedListener`を見てみます。

```java:AbstractAppBarOnDestinationChangedListener.java
@Override
public void onDestinationChanged(@NonNull NavController controller,
        @NonNull NavDestination destination, @Nullable Bundle arguments) {
    if (destination instanceof FloatingWindow) {
        return;
    }
    Openable openableLayout = mOpenableLayoutWeakReference != null
            ? mOpenableLayoutWeakReference.get()
            : null;
    if (mOpenableLayoutWeakReference != null && openableLayout == null) {
        controller.removeOnDestinationChangedListener(this);
        return;
    }
    CharSequence label = destination.getLabel();
    if (label != null) {
        // Fill in the data pattern with the args to build a valid URI
        StringBuffer title = new StringBuffer();
        Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");
        Matcher matcher = fillInPattern.matcher(label);
        while (matcher.find()) {
            String argName = matcher.group(1);
            if (arguments != null && arguments.containsKey(argName)) {
                matcher.appendReplacement(title, "");
                //noinspection ConstantConditions
                title.append(arguments.get(argName).toString());
            } else {
                throw new IllegalArgumentException("Could not find " + argName + " in "
                        + arguments + " to fill label " + label);
            }
        }
        matcher.appendTail(title);
        setTitle(title);
    }
    boolean isTopLevelDestination = NavigationUI.matchDestinations(destination,
            mTopLevelDestinations);
    if (openableLayout == null && isTopLevelDestination) {
        setNavigationIcon(null, 0);
    } else {
        setActionBarUpIndicator(openableLayout != null && isTopLevelDestination);
    }
}
```

見つけました、openableLayout がnull、つまり、DrawerLayoutとかがなく、isTopLevelDestinationがtrueの時にiconがnullでコールされていて、ボタンがなくなります。
`matchDestinations`が何をやっているかというと、

```java:NavigationUI.java
static boolean matchDestinations(@NonNull NavDestination destination,
        @NonNull Set<Integer> destinationIds) {
    NavDestination currentDestination = destination;
    do {
        if (destinationIds.contains(currentDestination.getId())) {
            return true;
        }
        currentDestination = currentDestination.getParent();
    } while (currentDestination != null);
    return false;
}
```

はい、ようするに`destinationIds`に`destination`が含まれるかを調べてますね。
では`destinationIds`つまり`mTopLevelDestinations` はどこから来ているのかというと、

```java:AbstractAppBarOnDestinationChangedListener.java
AbstractAppBarOnDestinationChangedListener(@NonNull Context context,
        @NonNull AppBarConfiguration configuration) {
    mContext = context;
    mTopLevelDestinations = configuration.getTopLevelDestinations();
```

はい、`AppBarConfiguration`ですね。
今度は、`AppBarConfiguration(navController.graph)`が何をやっているのか調べましょう。

```kotlin:AppBarConfiguration.kt
inline fun AppBarConfiguration(
    navGraph: NavGraph,
    drawerLayout: Openable? = null,
    noinline fallbackOnNavigateUpListener: () -> Boolean = { false }
) = AppBarConfiguration.Builder(navGraph)
    .setOpenableLayout(drawerLayout)
    .setFallbackOnNavigateUpListener(fallbackOnNavigateUpListener)
    .build()
```


```java:AppBarConfiguration.java
public Builder(@NonNull NavGraph navGraph) {
    mTopLevelDestinations.add(NavigationUI.findStartDestination(navGraph).getId());
}
```

ということで、NavigationGraphからstartDestinationを探し出して、そのIDがセットされています。

AppBarConfiguration.Builder()と引数なしでコールすると、以下のコンストラクタが呼び出され、`mTopLevelDestinations`が空になります。

```java:AppBarConfiguration.java
public Builder(@NonNull int... topLevelDestinationIds) {
    for (int destinationId : topLevelDestinationIds) {
        mTopLevelDestinations.add(destinationId);
    }
}
```

以上です