【SAPUI5】onExitで何をすればいいのか

##はじめに
コントローラーには4つのライフサイクルメソッドがあります。

- onInit
- onBeforeRendering
- onAfterRendering
- onExit

以下のブログで詳しく説明されています。
https://blogs.sap.com/2018/11/12/sapui5-controller-lifecycle-methods-explained/
このうち、onExitについては以下のように書かれていました。
>This method is called upon destruction of the View. The controller should perform its internal destruction in this hook. It is only called once per View instance, unlike the onBeforeRendering and onAfterRendering hooks.

ビューが削除されるときに呼ばれるフックで、コントローラーの内部的な削除をこの中で行うということです。

###疑問
私には以下の疑問がありました。
1. WebIDEで実行したときに、onExitを入れても呼ばれなかった。実際いつ呼ばれるのか
2. onExitの中で、具体的に何をする必要があるのか。しなかったらどうなってしまうのか

onExitと似た役割のものとして、Component.jsにdestroyというメソッドがあります。これの用途についても同じ疑問がありました。

1つ目の疑問については、[SAP Communty](https://answers.sap.com/questions/70764/when-onexit-will-be-executed.html)で検索したところ、アプリを抜けてラウンチパッドに戻るときに呼ばれることがわかりました。確かに、WebIDEでflpSandbox.htmlから実行した場合には呼ばれました（index.htmlで実行すると呼ばれない）。

###質問した結果
2つ目の疑問について[質問](https://answers.sap.com/questions/12973078/what-to-do-in-onexit-and-destroy-function.html)した結果、以下の回答をいただきました。

####onExit, destroyで何をするか
- onExitやdestroyは、ビューにバインドされていないコントロールを削除するときに使う
- 回答者の方は、主にカスタムライブラリで使用している
- ビューにバインドされていればビューと一緒に自動で削除されるが、バインドされていない場合はマニュアルで消す必要がある

####それをしないと、どんな悪影響があるか
- コントロールのidをハードコーディングしている場合、Launchpadからアプリに戻ったときにidの重複が発生する可能性がある

すなわち、コントロールのidがハードコードされていて、かつビューにバインドされていない場合、onExitやdestroyでコントロールを削除する必要があるということです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/3a2061d7-91ed-aacc-98ec-d39468951dc8.png)


##動作確認
以下のパターンで動作確認をしてみました。

1. onExitの検証
    2. idの重複が発生するパターン
    3. idの重複が発生しないパターン
        4. ビューにバインドする
        5. 任意のタイミングで削除する
        5. onExitで削除する
2. destroyの検証
    3. idの重複が発生するパターン
    4. idの重複が発生しないパターン
        5. destroyで削除する

###1. onExitの検証
####1-1. idの重複が発生するパターン
ダイアログ用のフラグメントを作成します。Dialogコントロールにはidをハードコーディングしています。

```xml
<core:FragmentDefinition
	xmlns="sap.m"
	xmlns:core="sap.ui.core">
	
	<Dialog
		id="dialog123"
		title="Sample Dialog">
		<content>
			<Text text="This is a sample Dialog" />
		</content>
		<beginButton>
			<Button type="Emphasized" text="OK" press="onClose"/>
		</beginButton>
	</Dialog>	
</core:FragmentDefinition>
```

ビューにバインドせずにダイアログを生成します。

```javascript
		onDialogOpen: function () {
			if(!this.oDialog) {
				this.oDialog = sap.ui.xmlfragment("demo.Train_24_onExit.fragment.Dialog", this);
				//this.getView().addDependent(this.oDialog); ビューにバインドしない
			}
			this.oDialog.open();
		},
		
		onClose: function () {
			this.oDialog.close();
		}
```

ラウンチパッドから起動します。（WebIDEのflpSandbox.htmlで実行）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/754d5d46-4593-6381-9500-35a453d5f68e.png)

ダイアログを出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/69150ebb-8958-1ca8-2364-e64a3d6ab7f4.png)

ラウンチパッドに戻り、もう一度実行するとid重複でエラーになります。このときDOMを確認しましたが、同じidの要素はありませんでした。見えないところで持っているのかもしれません・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/139848cd-af3f-80c7-ff5a-c1ccc4465387.png)

####1-2. idの重複が発生しないパターン
以下、いずれの方法でもidの重複が発生しなくなります。

#####1-2-1. ビューにバインドする
これが一般的な方法かと思います。

```javascript
		onDialogOpen: function () {
			if(!this.oDialog) {
				this.oDialog = sap.ui.xmlfragment("demo.Train_24_onExit.fragment.Dialog", this);
				//ビューにバインド
				this.getView().addDependent(this.oDialog);
			}
			this.oDialog.open();
		},
```
#####1-2-2. 任意のタイミングで削除する
ここでは、ダイアログを閉じたときにダイアログを消しています。

```javascript
		onClose: function () {
			//this.oDialog.close();
			this.oDialog.destroy();
		}
```
#####1-2-3. onExitで削除する

```javascript
		onExit: function () {
			this.oDialog.destroy();
		}
```
###2. destroyの検証
####2-1. idの重複が発生するパターン
カスタムライブラリを用意します。ボタンを表示するだけのシンプルな機能です。ボタンのidはハードコーディングしています。

```javascript:Component.js
sap.ui.define([
	"sap/ui/core/UIComponent",
	"sap/m/Button",
	"sap/m/MessageBox"
], function (UIComponent, Button, MessageBox) {
	"use strict";

	return UIComponent.extend("demo.library.zdemolibrary.myButton.Component", {

		metadata: {
			manifest: "json"
		},

		init: function () {
			UIComponent.prototype.init.apply(this, arguments);
		},
		
		destroy: function () {
			//UIComponent.prototype.destroy.apply(this, arguments);
		},
		
		createContent: function  () {
			var oButton = this._getButton();
			return oButton;
		},
		
		onPress: function () {
			MessageBox.alert("Component Button pressed!");
		},
		
		_getButton: function () {
			if (!this.oButton) {
				this._oButton = new Button("button123", { //idをハードコード
					text: "myButton",
					type: "Emphasized",
					press: this.onPress
				});
			}
			return this._oButton;
		}
	});
});
```

これをアプリに組み込みます。

```json:manifest.json
		"dependencies": {
			"minUI5Version": "1.65.6",
			"libs": {
				"sap.ui.layout": {},
				"sap.ui.core": {},
				"sap.m": {},
				"demo.library.zdemolibrary": {
					"async": true
				}
			},
			"components": {
				"demo.library.zdemolibrary.myButton": {
					"lazy": true
				}
			}
		},
		"componentUsages": {
			"myButton": {
				"name": "demo.library.zdemolibrary.myButton",
				"settings": {},
				"componentData": {},
				"manifest": true
			}
		}
```

```javascript:App.controller.js
		onInit: function () {
			this._loadComponent();
		},

		_loadComponent: function () {
			this.getOwnerComponent().createComponent({
				usage: "myButton",
				settings: {},
				componentData: {},
				async: true
			}).then(function(oComp) {
				this.byId("box").addItem(new ComponentContainer({
					component: oComp
				}));                         
			}.bind(this)).catch(function(oError) {
				MessageBox.error(oError.message);
			});
		}
```
```xml:App.viel.xml
	<App id="app">
		<pages>
			<Page id="page" title="{i18n>title}">
				<content>
					<VBox id="box">
					</VBox>
				</content>
			</Page>
		</pages>
	</App>
```

ラウンチパッドから起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b11f079a-ffdc-fa79-4eed-9f7e735b7c37.png)

ラウンチパッドに戻り、もう一度実行するとid重複でエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/bd2d0a27-2a53-cea4-3bd7-e77cef5f923c.png)

####2-2. idの重複が発生しないパターン
#####2-2-1. destroyで削除する
ライブラリのComponent.jsのdestroyで、コンポーネントを削除します。

```javascript:Component.js
		destroy: function () {
			UIComponent.prototype.destroy.apply(this, arguments);
		}		
```

##まとめ
ビューのコントロールは、コントローラーから操作する都合上、idをつけることが多いと思います。そのときは、ビューにバインドするようにすれば、onExitでは特に何もしなくても大丈夫です。
カスタムライブラリは、ビューにバインドされるとは限らないので、常にdestroyを実行したほうがいいと思いました。