JetpackのNavigationのオプション、NavOptionsをざっくり覗いてみる

# はじめに
JetPackのNavigation、みなさん使ってますか？
まだα版ですが可能性を非常に感じますね。

さて、Navigationをいざプロジェクトに載せようと思った時、「この設定、どうすりゃいいんだ？」ってことなかったですか？例えば↓のような起動オプションの設定。

```kotlin:SomeActivity
  val intent = Intent(context, SomeActivity::class.java)
  intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
  context.startActivity(intent)
```

大丈夫、（多分）[NavOptions](https://developer.android.com/reference/androidx/navigation/NavOptions)を使えばできます。

今回はケースごとにTips形式でどんどん上げていこうと思います。

**なお、これを書いている時点でalpha2であること、いくつか非推奨メソッドが存在していること、ざっくりとしか確認してないので、期待動作と異なる可能性や、今後実装が大きく変わる可能性も十分あるので注意**

# 起動フラグを設定する

## FLAG_ACTIVITY_SINGLE_TOPで起動したい
*メソッド呼び出し確認のみ*

```kotlin:BeforeActivity 
  val intent = Intent(context, SomeActivity::class.java)
  intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
  context.startActivity(intent)
```

```kotlin:AfterActivity
  view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
  val options = NavOptions.Builder().setLaunchSingleTop(true).build()
  Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
        }
```

### 実装ポイント
- `setLaunchSingleTop(true)`を設定し、navigateの引数に渡す

### 該当する処理

```java:ActivityNavigator.java
    @SuppressWarnings("deprecation")
    @Override
    public void navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions) {
/* 略 */
        if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        }
/* 略 */
}
```


## FLAG_ACTIVITY_NEW_DOCUMENTで起動したい
*動作確認してないので注意*


```kotlin:BeforeActivity 
  val intent = Intent(context, SomeActivity::class.java)
  intent.flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT
  context.startActivity(intent)
```

```kotlin:AfterActivity（非推奨な方法)
  view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
  val options = NavOptions.Builder().setLaunchDocument(true).build()
  Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
        }
```

上記のSingleTopと同じような指定方法で良さげと思いきや、setLaunchDocument()が非推奨指定されてる。んで、setLaunchDocumentのJavadoc見ると以下のような記載が。

```java:Activit
        /**       
         * 略                                                                                                                                                                   
         * @param launchDocument true to launch a new document task                                 
         * @deprecated As per the {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}         
         * documentation, it is recommended to use {@link android.R.attr#documentLaunchMode} on an  
         * Activity you wish to launch as a new document.                                           
         */                                                                                         
        @Deprecated                                                                                 
        @NonNull                                                                                    
        public Builder setLaunchDocument(boolean launchDocument) {/* 略 */}                               
```

documentLaunchModeで指定してね、とのこと。なので↓の方法がよさげ。

```xml:AndroidManifest.xml
        <activity android:name=".Main2Activity"
            android:documentLaunchMode="always">
        </activity>
```

### 実装ポイント
AndroidManifest.xmlで指定するだけ

### 該当する処理
alpha2版ではまだ残ってるけど、使われているメソッドがすでに非推奨になっているため、参考程度に。

```java:ActivityNavigator.java
    @SuppressWarnings("deprecation")
    @Override
    public void navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions) {
/* 略 */
        if (navOptions != null && navOptions.shouldLaunchDocument()
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
        } else if (!(mContext instanceof Activity)) {
            // If we're not launching from an Activity context we have to launch in a new task.
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
/* 略 */
}
```

## LAUNCH_CLEAR_TASKで起動したい
*メソッド呼び出し確認のみ*

```kotlin:BeforeActivity 
  val intent = Intent(context, SomeActivity::class.java)
  intent.flags = Intent.LAUNCH_CLEAR_TASK
  context.startActivity(intent)
```

```kotlin:AfterActivity（非推奨な方法)
  view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
  val options = NavOptions.Builder().setClearTask(true).build()
  Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
        }
```

setLaunchDocument同様、こちらもすでに非推奨。同じようにJavadocを読んで見る。

```java:ActivityNavigator.java
        /**                                                                                       
         * Clear the entire task before launching this target. If you are launching as a          
         * {@link #setLaunchDocument(boolean) document}, this will clear the document task.       
         * Otherwise it will clear the current task.                                              
         *                                                                                        
         * @param clearTask                                                                       
         * @return                                                                                
         * @deprecated Use {@link #setPopUpTo(int, boolean)} with the                             
         * {@link NavDestination#getId() id} of the                                               
         * {@link androidx.navigation.NavController#getGraph() NavController's graph}             
         * and set inclusive to true.                                                             
         */                                                                                       
        @Deprecated                                                                               
        @NonNull                                                                                  
        public Builder setClearTask(boolean clearTask) {                                          
```

`setPopUpTo(int, boolean)`使ってね。`id`には`NavController`の`Id`を、`inclusive`には`true`を指定してね、とのこと。念のため`setPopUpTo`のJavadocも確認。

```java:ActivityNavigator.java
        /**                                                                                          
         * Pop up to a given destination before navigating. This pops all non-matching destinations  
         * from the back stack until this destination is found.                                      
         *                                                                                           
         * @param destinationId The destination to pop up to, clearing all intervening destinations. 
         * @param inclusive true to also pop the given destination from the back stack.              
         * @return this Builder                                                                      
         * @see NavOptions#getPopUpTo                                                                
         * @see NavOptions#isPopUpToInclusive                                                        
         */                                                                                          
        @NonNull                                                                                     
        public Builder setPopUpTo(@IdRes int destinationId, boolean inclusive) {                     
```
`destinationId`に指定したIdを持つ画面までをクリアする？っぽい？(自信ない)
なので↓のように指定すれば良さげ。

```kotlin:AfterActivity
  view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
  val options = NavOptions.Builder().setPopUpTo(R.id.main2Activity, true).build()
  Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
        }
```

### 実装ポイント
`setPopUpTo(destinationId, inclusive)`を使う

### 該当する処理

```java:NavController.java
    public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions) {
/* 略 */
        if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != 0) {
            popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
            return;
        }
/* 略 */
    }

    /**
     * Attempts to pop the controller's back stack back to a specific destination.
     *
     * @param destinationId The topmost destination to retain
     * @param inclusive Whether the given destination should also be popped.
     *
     * @return true if the stack was popped at least once, false otherwise
     */
    public boolean popBackStack(@IdRes int destinationId, boolean inclusive) {
        if (mBackStack.isEmpty()) {
            throw new IllegalArgumentException("NavController back stack is empty");
        }
        ArrayList<NavDestination> destinationsToRemove = new ArrayList<>();
        Iterator<NavDestination> iterator = mBackStack.descendingIterator();
        while (iterator.hasNext()) {
            NavDestination destination = iterator.next();
            if (inclusive || destination.getId() != destinationId) {
                destinationsToRemove.add(destination);
            }
            if (destination.getId() == destinationId) {
                break;
            }
        }
        boolean popped = false;
        iterator = destinationsToRemove.iterator();
        while (iterator.hasNext()) {
            NavDestination destination = iterator.next();
            // Skip destinations already removed by a previous popBackStack operation
            while (!mBackStack.isEmpty() && mBackStack.peekLast().getId() != destination.getId()) {
                if (iterator.hasNext()) {
                    destination = iterator.next();
                } else {
                    destination = null;
                    break;
                }
            }
            if (destination != null) {
                popped = destination.getNavigator().popBackStack() || popped;
            }
        }
        return popped;
    }
```


# 起動アニメーションを設定する

## 画面切り替え時
*動作確認OK!*

beforeの状態は省略(~~書くのがﾒﾝﾄﾞｸｻｸﾅｯﾀ~~)

```kotlin:AfterActivity
  view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
            val options = NavOptions.Builder()
                    .setEnterAnim(R.anim.abc_fade_in)
                    .setExitAnim(R.anim.abc_fade_out)
                    .build()
  Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
        }
```

### 実装ポイント
- `setEnterAnim/setExitAnim`を使う

### 該当する処理

```java:ActivityNavigator.java
    @SuppressWarnings("deprecation")
    @Override
    public void navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions) {
/* 略 */
           if (navOptions != null && mHostActivity != null) {
            int enterAnim = navOptions.getEnterAnim();
            int exitAnim = navOptions.getExitAnim();
            if (enterAnim != -1 || exitAnim != -1) {
                enterAnim = enterAnim != -1 ? enterAnim : 0;
                exitAnim = exitAnim != -1 ? exitAnim : 0;
                mHostActivity.overridePendingTransition(enterAnim, exitAnim);
            }
        }
}

```

## BackStackからの復帰時
*動作確認してないので注意*
beforeの状態は(ry


```kotlin:AfterActivity
  view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
            val options = NavOptions.Builder()
                    .setPopEnterAnim(R.anim.abc_fade_in)
                    .setPopExitAnim(R.anim.abc_fade_out)
                    .build()
  Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
        }
```

### 実装のポイント
- `setPopEnterAnim/setPopExitAnim`を使う


### 該当する処理

```java:ActivityNavigator.java
    @SuppressWarnings("deprecation")
    @Override
    public void navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions) {
/* 略 */
        NavOptions.addPopAnimationsToIntent(intent, navOptions);
/* 略 */
}
```

```java:NavOptions.java
    /**                                                                                              
     * Add the {@link #getPopEnterAnim() pop enter} and {@link #getPopExitAnim() pop exit}           
     * animation to an Intent for later usage with                                                   
     * {@link #applyPopAnimationsToPendingTransition(Activity)}.                                     
     * <p>                                                                                           
     * This is automatically called for you by {@link ActivityNavigator}.                            
     * </p>                                                                                          
     *                                                                                               
     * @param intent Intent being started with the given NavOptions                                  
     * @param navOptions NavOptions containing the pop animations.                                   
     * @see #applyPopAnimationsToPendingTransition(Activity)                                         
     * @see #getPopEnterAnim()                                                                       
     * @see #getPopExitAnim()                                                                        
     */                                                                                              
    public static void addPopAnimationsToIntent(@NonNull Intent intent,                              
            @Nullable NavOptions navOptions) {                                                       
        if (navOptions != null) {                                                                    
            intent.putExtra(KEY_NAV_OPTIONS, navOptions.toBundle());                                 
        }                                                                                            
    }                                                                                                
                                                                                                     
    /**                                                                                              
     * Apply any pop animations in the Intent of the given Activity to a pending transition.         
     * This should be used in place of  {@link Activity#overridePendingTransition(int, int)}         
     * to get the appropriate pop animations.                                                        
     * @param activity An activity started from the {@link ActivityNavigator}.                       
     * @see #addPopAnimationsToIntent(Intent, NavOptions)                                            
     * @see #getPopEnterAnim()                                                                       
     * @see #getPopExitAnim()                                                                        
     */                                                                                              
    public static void applyPopAnimationsToPendingTransition(@NonNull Activity activity) {           
        Intent intent = activity.getIntent();                                                        
        if (intent == null) {                                                                        
            return;                                                                                  
        }                                                                                            
        Bundle bundle = intent.getBundleExtra(KEY_NAV_OPTIONS);                                      
        if (bundle != null) {                                                                        
            NavOptions navOptions = NavOptions.fromBundle(bundle);                                   
            int popEnterAnim = navOptions.getPopEnterAnim();                                         
            int popExitAnim = navOptions.getPopExitAnim();                                           
            if (popEnterAnim != -1 || popExitAnim != -1) {                                           
                popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;                                
                popExitAnim = popExitAnim != -1 ? popExitAnim : 0;                                   
                activity.overridePendingTransition(popEnterAnim, popExitAnim);                       
            }                                                                                        
        }                                                                                            
    }         
```

# 終わりに
「はじめに」にも書いたけど、この辺りは今後大きく変わっていく可能性が大きそう。
なので、覚えたことが無駄になるのはチョットナ・・・という人は、alphaが取れたことにもう一度見ると良いかも。