Monaca/Cordova/OnsenUI/Angular2+を半年ほど使ってみてのTips

# これはなに

Monaca/Cordova/Onsen UI/Angular2+を使ってAndroid/iOS向けスマートフォンアプリを作成する機会があったので，その際に得た知見をまとめておきます．

# 前提

* MonacaはEnterpriseプランを利用している．その他のプランとは違う部分があるかも
    * https://ja.monaca.io/pricing.html
* 今回対象としたプラットフォームはAndroid 5.0以上，iOS10.0以上（iPad含む）．それ以下のバージョンについてはこの記事ではほぼ考慮していない
* プロジェクト開始前の自分のスペック
    * Webフロント開発：ES5とjQueryは何回か触ったことがある．最近のJavaScript（というか最近のWebフロント技術）はほぼ初見．レスポンシブデザイン？，ああBootstrap使うやつね
    * スマートフォンアプリ開発：業務での開発経験はなし．Android/iOSともにモックレベルのものを作ったことがあるだけ
* 思いついた順で書いているので，章立ては結構適当

# 環境

* Monaca (Localkit) 3.2.0
* Apache Cordova 7.1.0
* Onsen UI 2.10.8
* Angular 6.0.4

# 読んでおいた方がいい資料

## 公式ドキュメント

* Monaca: https://docs.monaca.io/ja/
* Cordova: https://cordova.apache.org/
    * 日本語版もあるが，ページによっては翻訳がかなりヤバいので，できれば英語で読んだ方がよい
* Onsen UI: https://ja.onsen.io/
* Angular: https://angular.jp/
    * Angularはバージョン1 (angularjsとよく呼ばれる)とバージョン2以降（単にAngularと呼ばれる）で全くアーキテクチャが異なることに注意．（ググるとバージョン1の記事が多く出るが，）バージョン1は2021年にサポートが切れるようなので，今から始めるならバージョン2以降をおすすめする
* Ionic: https://ionicframework.com/
    * IonicはCordovaをベースとした技術のため，生Cordovaでの開発時にも結構ドキュメントが参考になる．今回の開発だとAngularを使ったのでよけいに
    * ググるときも"cordova hogehoge"だけじゃなく，"ionic hogehoge"を試してみると結構当たったりする

## Github

* Monaca: https://github.com/monaca
    * テンプレートのソースコードだったりCLIやLocalkitのコードだったりがある
* Onsen UI: https://github.com/OnsenUI/OnsenUI
    * そんなに複雑なコードじゃないのでハマったら最悪読める
* Angular: https://github.com/angular
    * こっちは知識がないとちょっと厳しい…

## コミュニティ・フォーラム系（ハマったとき用）

* stack overflow: https://stackoverflow.com/
* Monaca & Onsen UI Community Forum: https://community.onsen.io/
* Onsen UI Discord: https://discordapp.com/invite/WANew4d
* Monaca UG: https://monacaug.connpass.com/
* Ionic Japan User Group: https://ionic-jp.connpass.com/
* Angular Japan User Group: https://community.angular.jp/

あとはつよい人達のTwitterとか，MDNのドキュメントとかをよく読んでいた．

# Monaca関連

* Monacaについては，Monaca Cloud（以下，単に"Cloud"）側で随時バージョンアップが施されるので，ここの内容はわりとすぐに古くなると考えられる．
* 以下，公式ドキュメント等に記載がなく，挙動やログから推測しただけのものが多い．利用は自己責任で（事実誤認があったら教えていただけると嬉しいです）．

## ビルドについて

ここでは，デフォルトのMonacaプロジェクトテンプレートでのビルドの手順について概要を記載する．
Monacaのプロジェクトテンプレートは以下に存在する．

https://github.com/monaca-templates

1. （Angular，Vue，Reactなど，JavaScriptへのトランスパイルを必要とするプロジェクトのみ，）事前に `monaca transpile` コマンドを実行する．

    デフォルトのテンプレートでは，これがNPMのスクリプト `npm run monaca:transpile` として実行され，さらに `npm run build` として実行される．このあたりの動きは，package.jsonを変更することでカスタマイズ可能であり，例えば，CI目的でLinterや単体テストをビルド前に噛ませることもできる．
    `npm run build` 内で `webpack --mode production` が実行され，Webpackによるビルドが行われる．

    Webpackの成果物はすべて`www/`以下に入っていることを仮定している（ような動きをしており，このあたりをカスタマイズするのは若干危険そう）．

2. Monaca CLIやMonaca Localkitを使っている場合は，ここでCloud側とのデータ同期が行われる．

3. Cloudとのデータ同期後，ユーザ側でビルドの設定と実行を行う．詳しくは公式ドキュメントで．

    https://docs.monaca.io/ja/products_guide/monaca_ide/build/

4. 必要なファイルをプロジェクトからビルドサーバにコピーする．

    挙動を見る限り，Monacaはプロジェクトごとに一意なディレクトリをビルドサーバに作成し，その上でビルドを動かしていると考えられる．

    ここで，`src`ディレクトリなど，ビルドに直接的に関連しないディレクトリについては**コピーされない**ことに注意．例えば相対パス指定でこれらのディレクトリを指定していると，「ローカル環境では動くがCloudに上げると動かない」現象が発生する．

5. Cloud上で，`npm install --production`を実行する．

    これにより，Cloud側の仮想マシン上に必要なNPMの依存がインストールされる．

6. Cloud上で，`cordova platform add ${ビルド先のプラットフォーム}@${プラットフォームのバージョン}`を実行する．

    現状のCordova 7.1向けの構成だと，cordova-ios: 4.5.4，cordova-android: 6.4.0が使われるらしい．特にOSSのカスタムプラグインを試す際は，バージョン互換に気をつける必要がある．

    https://press.monaca.io/takuya/1550

7. Cloud上で，`cordova prepare`を実行する．これにより，package.json，config.xmlの記述に応じて必要な設定ファイルやプラグイン等を更新している．

8. Cloud上で，`cordova compile`を実行する．これによりビルドが完了し，成果物が生成される．

## DeployGateとの連携

以下のような手順で，MonacaによってビルドしたアプリをそのままDeployGateにアップロードできる．
アプリを共有する際に非常に便利な機能．

https://docs.monaca.io/ja/products_guide/monaca_ide/monaca_ci/supported_services/#deploygate

一点ドキュメントにないハマりどころとして，DeployGateに上げたいMonacaプロジェクトを他人と共有している場合，DeployGateにアップロードできるのはプロジェクトの所有者だけになる，という点がある．

## Monacaデバッガー

本来毎度ビルドして実機にインストールして……を繰り返すスマホアプリのデバッグを，爆速で行うための非常に強力なアプリ．
これを使いこなせるかどうかで開発速度が大きく変わる．

Monaca Cloudと連携することで，アプリのソースコードの変更を即座に実機にインストールしたアプリに反映できる．
また，実機とPCをUSBケーブルかBluetoothで接続することで，実機にインストールしたアプリについて，Chrome/SafariのDeveloper Consoleを見ながらデバッグすることができる．

ストア版とカスタムビルド版の2種類が存在し，ストア版は無料枠で使える分機能制限がある．ここではカスタムビルド版について記す．

https://ja.monaca.io/debugger.html
https://docs.monaca.io/ja/products_guide/debugger/debug/#usb-%E3%83%87%E3%83%90%E3%83%83%E3%82%B0

* カスタムビルド版のMonacaデバッガーを使う際は，デバッガーをビルドする際に，必要なCordovaプラグインをすべて組み込んだMonacaプロジェクトを使う必要があることに注意．例えば，2つ以上のMonacaプロジェクトが存在する状態でMonacaデバッガーをビルドした際，ビルドしたプロジェクトに含まれていないCordovaプラグインについては，そのプラグインを含む他のプロジェクトのアプリを起動しても利用できない．ここは結構ハマる
* MonacaデバッガーではPush通知機能は利用できない
    * https://docs.monaca.io/ja/products_guide/push_notification/overview/#%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E9%80%9A%E7%9F%A5%E3%81%AE%E8%A8%AD%E5%AE%9A-monaca-%E4%B8%8A%E3%81%A7%E3%81%AE%E5%87%A6%E7%90%86
* アプリのソースコードを変更，反映しても，LocalStorageについては端末内にキャッシュされる．消去したい時は，アプリ内の「設定」->「ローカルストレージをクリア」を選択する
* アプリ内の「設定」->「メモリ使用量表示」をONにすることで，アプリで利用しているメモリが把握できる．メモリリークの調査をしたい時に便利
* ときおり，「Monacaデバッガーでは動く/動かないが，ビルドしたアプリを実機にインストールすると動かない/動く」ことがある．最終的なテストについてはビルドしたアプリを使うこと


# Cordova関連

## Android/iOSで別のpackageを使う

すでにストアに出ているアプリをリニューアルする場合など，Android版のApp IDとiOS版のBundle IDを異なる値にする必要がある場合がある．

基本的には，公式ドキュメントにあるように，

https://docs.monaca.io/ja/faq/application/#app-id-ios-%E5%81%B4-%E3%81%A8-%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E5%90%8D-android-%E5%81%B4-%E3%81%AB%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E7%95%B0%E3%81%AA%E3%82%8B%E5%80%A4%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF

```config.xml
<!--
  android-packageName: AndroidのApp ID
  ios-CFBundleIdentifier: iOSのBundle ID
-->
<widget xmlns="http://www.w3.org/ns/widgets"
    android-packageName="com.example.android"
    ios-CFBundleIdentifier="com.example.ios"
    version="1.0.0”>
...
```

とすればOK．

> なお、このような設定にした場合、 Android 向けのカスタムビルド版デバッガー のビルド処理が失敗することが確認されています。他のビルド処理では、このような不具合は確認されていません。

とも書いてあるが，現時点で，自分の手元でビルドにこれが起因と考えられる不具合を発見したことはない．

## バイナリのバージョン管理

ストアに出すバイナリについて，アップロードされているバージョンをちゃんと管理したい．

特に，各プラットフォームで，「ユーザから見えるバージョン」と，「内部管理用のバージョン」が異なることに気をつける必要がある．

|  | ユーザから見えるバージョン | 内部管理用のバージョン |
|:-----------:|:------------:|:------------:|
| Android | AndroidManifest.xmlのversionName | AndroidManifest.xmlのversionCode |
| iOS | ${PROJECT_NAME}-Info.plistのCFBundleShortVersionString | ${PROJECT_NAME}-Info.plistのCFBundleShortVersionStringとCFBundleVersionの組合せ |

どちらのプラットフォームでも，「内部管理用のバージョン」が同一かそれ以下のバイナリは，ストアにアップロードできない仕様になっているらしい（未検証）．


https://qiita.com/Nkzn/items/44702b93751015b4811c

上記の記事に書いてある通り，Cordovaによって生成したバイナリのバージョンについては，

```config.xml
<!--
  version: アプリのバージョン．AndroidではversionName，iOSではCFBundleShortVersionStringに変換される
  android-versionCode: versionCode．省略した際は上記記事のロジックで自動生成されるらしい
  ios-CFBundleVersion: CFBundleVersion．省略した際はversionと同じ値が使われる
-->
<widget
   id="com.example.my-app"
   version="0.1.0"
   android-versionCode="12345"
   ios-CFBundleVersion="25.20151124"
   xmlns="http://www.w3.org/ns/widgets"
   xmlns:cdv="http://cordova.apache.org/ns/1.0">
```

上記のように設定できる．

個人的には，よくわからない自動生成のロジックによってバイナリがアップロードできなくなるリスクを考えるぐらいなら，多少面倒でも手動で設定しておいた方がよいように思う．

ちなみに，config.xmlに設定できる内容は

https://cordova.apache.org/docs/en/7.x/config_ref/index.html

にすべて書いてあるので，一度目を通してみるとよい（けっこうわかりにくいけれど）．

## スプラッシュ画面

いずれ書く．

https://cordova.apache.org/docs/en/7.x/reference/cordova-plugin-splashscreen/index.html
https://medium.com/@photokandy/phonegap-build-supports-ios-launch-storyboards-44a4180bfafe

## 画面の解像度について

いずれ書く．

## 端末の画面回転を無効にする

画面の回転が不要なアプリだったため，Android/iOSともにPortrait（縦画面）で固定したかった．

最初にconfig.xmlの`Orientation`の設定を`portrait`に変更して試してみたが，iOSで画面を上下逆さにした時に画面も上下逆さになる現象に遭遇した．

原因を調査したところ，config.xmlの`Orientation`の設定を`portrait`に変更するだけだと，Info.plist内の`UIInterfaceOrientationPortrait`（上下も含めて正しい向き）に加えて，`UIInterfaceOrientationPortraitUpsideDown`（上下逆さ）も設定されてしまうらしい．


https://developer.apple.com/documentation/uikit/uiinterfaceorientation/uiinterfaceorientationportrait?language=objc
https://developer.apple.com/documentation/uikit/uiinterfaceorientation/uiinterfaceorientationportraitupsidedown?language=objc

https://qiita.com/keeey/items/b5860ca685ac000ccf34

上記記事のように，iOSについて，縦画面固定で，かつ上下逆さにもしたくない場合は，Info.plistを直接編集する必要がある．

```config.xml
<widget ...>
  <preference name="Orientation" value="portrait"/> <!-- Android向け -->

  <platform name="ios">
    <custom-config-file parent="UISupportedInterfaceOrientations" target="*-Info.plist" mode="replace">
      <array>
        <string>UIInterfaceOrientationPortrait</string>
      </array>
    </custom-config-file>
    <custom-config-file parent="UISupportedInterfaceOrientations~ipad" target="*-Info.plist" mode="replace">
      <array>
        <string>UIInterfaceOrientationPortrait</string>
      </array>
    </custom-config-file>
  </platform>
</widget>
```

（さらにややこしいことに，）iPhoneとiPadで設定項目が別なので，両方をサポートする場合には上記の2つの設定変更が必要．

## [Android]ビルド時にDexIndexOverflowExceptionが出る

Androidのネイティブ開発をやったことがあれば一度はハマったことがあると思うが，Androidのプロジェクトは，デフォルトの設定だと65,536個を超えるメソッドを持てないという制約がある．
この数には外部ライブラリに存在するメソッドも含まれるため，Cordovaプラグインを多く使ったアプリであれば，容易に超えてしまう数字となる．
この制約に違反すると，ビルド時に`DexIndexOverflowException`という例外が発生する．

https://stackoverflow.com/questions/37944696/cordova-run-android-fails-with-com-android-dex-dexindexoverflowexception-method
https://developer.android.com/studio/build/multidex?hl=ja

回避策として，以下の2つがある．

1. `minSdkVersion`を21以上にする．Android 5.0以上を対象にしたアプリであれば，これで問題ない

    ```config.xml
    <widget ...>
      <preference name="android-minSdkVersion" value="21"/>
    </widget>
    ```

2. `cordova-plugin-enable-multidex`を導入する（このプラグインは導入するだけでよく，特段の設定は不要）．
    https://github.com/adriano-di-giovanni/cordova-plugin-enable-multidex

## [Android]Auto Backup for Appsを無効化する

Auto Backup for Appsは，Android 6.0から導入された，自動的にアプリのデータをGoogle Driveにバックアップする機能．

https://developer.android.com/guide/topics/data/autobackup#EnablingAutoBackup

この機能はデフォルトでONになっているが，アプリのアップデート時などにゴミデータを残してしまうので，要件として無効にしておきたかった．

```config.xml
<widget ...>
  <platform name="android">
    <edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application">
      <application android:allowBackup="false"/>
    </edit-config>
  </platform>
</widget>
```

上記のようにする．

基本的にAndroidManifest.xmlを書き換える場合は，`cordova-custom-config`を使うとよい．

https://github.com/dpa99c/cordova-custom-config


## [iOS]アイコンに表示される名前を日本語にする

端末上に表示されるアプリの名前（アイコンの下に表示されるもの）は，config.xmlの`name`の値になる．
https://cordova.apache.org/docs/en/7.x/config_ref/index.html#name

微妙なハマりどころだが，cordova-iosだと，ビルドの成果物として生成されるXcodeプロジェクトファイル（`.xcodeprj`ファイル）の名前に，この値と同じ値を利用しようとするらしい．
Xcodeプロジェクトファイルでは，非ASCIIの文字を持つファイル名が認められていないため，このままでは日本語のアプリ名を持つアプリがビルドできなくなってしまう．

回避策として，

```config.xml
<widget ...>
  <name>EnglishAppName</name>

  <platform name="ios">
    <config-file parent="CFBundleDisplayName" platform="ios" target="*-Info.plist">
        <string>日本語のアプリ名</string>
    </config-file>
  </platform>

  <platform name="android">
    <name>日本語のアプリ名</name> <!-- Androidでは`name`に日本語を入れられるので，上の値を上書きするだけにする -->
  </platform>
</widget>
```

のように，プラットフォームごとに個別に名前を設定するようにした．

`CFBundleDisplayName`が端末上に表示されるアプリの名前を指す．

（未検証だが）short nameを使えばもっと簡単に解決できたのかも…
https://cordova.apache.org/docs/en/7.x/config_ref/index.html#short-name

## [iOS]CordovaのエンジンとしてWKWebViewを利用する

前提として，Cordovaは，対象プラットフォームのWebView上でWebプロジェクトを動作させている．

https://github.com/apache/cordova-ios

CordovaとiOSのブリッジライブラリであるcordova-iosでは，現状UIWebViewがエンジンとして使われているが，UIWebViewには，

1. Deprecatedになっており，いつ廃止されるかも予断を許さない
    https://developer.apple.com/documentation/uikit/uiwebview
2. セキュリティ的に脆弱
3. 動作が遅い
4. クラッシュ率が高い

などの問題がある．

Cordovaコミュニティの中でも，後発のWKWebViewに移行することは考慮されており，プラグインという形で提供されている．
どうやら今後のcordova-iosではこちらがデフォルトとしてサポートされる方針らしい．

https://github.com/apache/cordova-plugin-wkwebview-engine
https://cordova.apache.org/news/2018/08/01/future-cordova-ios-webview.html

今回のプロジェクトには入れられなかったが，ちょっとだけ調査したことを残しておく．

`cordova-plugin-wkwebview-engine`の導入については，プラグインをインストールした上で，

```config.xml
<widget ...>
 <platform name="ios">
   <feature name="CDVWKWebViewEngine">
     <param name="ios-package" value="CDVWKWebViewEngine" />
   </feature>
   <preference name="CordovaWebViewEngine" value="CDVWKWebViewEngine" />
  </platform>
</widget>
```

のような記述を追加する必要がある．
本当にWKWebViewが使われているか確認したい場合は，XcodeプロジェクトのログをXcodeで確認するとよい．

* たしかに画面のちらつきは減っているような気がする．`ons-pull-hook`のバグとかスクロール関連の問題も改善されていそう
* 自分の手元では外部のAPIの呼び出しができなかった（UIWebView環境ではできていた）．CORS制約に引っかかっている可能性がある．設定の見直しでどうにかなるのかは不明

## [iOS]リリースする端末を限定する

いずれ書く．

## [iOS]虫眼鏡を無効化する

詳細な理由まではわからなかったが，iOS端末でテキストをロングタップすると，まれに虫眼鏡のUI部品が表示される．
不要なのでこれを無効化したかった．

https://stackoverflow.com/questions/32812308/ios-cordova-long-press-shows-text-select-magnifying-glass-even-with-text-selecti
https://stackoverflow.com/questions/32720848/global-disable-magnifying-glass-on-ios-9-in-meteor-cordova-app

上記の記事にあるように，

```config.xml
<widget ...>
  <preference name="SuppressesLongPressGesture" value="true" />
</widget>
```
を入れると無効化される．

## [iOS]ハイフンがリンクになるのを無効化する

iOS端末で半角数字に`-`が混ざった文字列（自分の場合はCopyrightの`2010-2019`のような文字列）を表示しようとすると，自動的にその部分にaタグが挿入されてしまう問題に遭遇した．

iOS Safariの仕様で，「電話番号っぽい文字列」に勝手に電話リンクを挿入するようになっているらしい．
余計なお世話なので無効にしておく．

https://qiita.com/megurock/items/edbd9db255eb167712c6

```html:src/public/index.html.ejs
<!DOCTYPE html>
<html>
<head>
  ...
  <meta name="format-detection" content="telephone=no"> <!-- 電話リンクを無効化 -->
  ...
</head>
...
</html>
```

iOS Safariは謎仕様が多いので疲れる……

# Onsen UI関連

## 画面遷移系のメソッド群についてのTips

いずれ書く．

## イベントが発生するタイミング

いずれ書く．

## iPhone Xシリーズ対応

（完全には調べきれなかっので不正確な部分はあるかと思うが，いちおう動きはしたのでやったことを書いておく）

デフォルトの設定のまま，iPhone X以降のiPhoneのハイエンド端末（iPhone XS, XS Max, XRなど）でアプリを動作させると，上下（横画面の場合は左右）に黒い帯状の余白が表示されてしまう．

![_thumb_2186284.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46811/0142fa5f-533d-0269-1878-209cceeb9fa0.png)
![_thumb_2186285.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46811/7d6c9282-bc33-c787-43cd-5d270cbd8be7.png)

これをなくし，他の端末と同様に見えるようにしたい．

iPhone Xが発売されてからのiOSにはSafe Areaという概念が導入されている．
これは，文字通り「安全な領域」であり，画面内にノッチ（切り欠き）がある端末であっても，その領域にかぶらない形でUI部品を配置することができるようになっている．

https://medium.com/@n_matagawa/iphone-x-%E3%81%AB%E5%AF%BE%E5%BF%9C%E3%81%97%E3%81%9F%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB%E3%82%B5%E3%82%A4%E3%83%88-cordova-%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9%E3%81%A8%E6%B3%A8%E6%84%8F%E7%82%B9-7f04bf24df80
https://blog.phonegap.com/displaying-a-phonegap-app-correctly-on-the-iphone-x-c4a85664c493

上記の参考記事にも書いてあるが，基本的に注意すべき点は2点．

1. iOSのアプリをiPhone Xシリーズで動作させる場合，端末に応じた解像度のスプラッシュ画面（Launch Screen）の画像が用意されていないと，画面をSafe Area内に縮小して表示してしまう仕様が存在する．iPhone Xシリーズに対応する際は，`cordova-plugin-splashscreen`を導入した上で，必要な画像を登録すること

    ```config.xml
    <widget ...>
      <platform name="ios">
        <splash src="/res/ios/screen/Default@2x~universal~anyany.png"/> <!-- 画像をres/ios/screen以下に配置する -->
        <!-- 他のsplashタグの要素はすべて消す -->
      </platform>
    </widget>
    ```

    ここでは，Storyboardベースのスプラッシュ画面登録を行っている．スプラッシュ画面を特に凝ったデザインにしないのであればこれでよいはず．

2. CordovaのiOSアプリは内部的にiOS Safariのレンダリングエンジンに依存しているため，1.とは別に，Safari用のiPhone X対応をする必要がある．具体的には，
    a. `viewport-fit=cover`をmetaタグに付与する

    ```html:src/public/index.html.ejs
    <!DOCTYPE html>
    <html>
    <head>
      ...
      <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"> <!-- ←ここ -->
      ...
    </head>
    ...
    </html>
    ```

    b. アプリの初期化タイミングでOnsen UIのiPhone X用パッチを当てる

    ```javascript
    if (ons.platform.isIPhoneX()) {
      document.documentElement.setAttribute('onsflag-iphonex-portrait', '');
      document.documentElement.setAttribute('onsflag-iphonex-landscape', '');
    }
    ```

    https://ja.onsen.io/v2/guide/iphonex.html#iphone-x

![_thumb_2186288.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46811/856fde9f-8e2f-199e-cd27-263ad199b3f2.png)
![_thumb_2186289.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46811/8959de80-aaf3-3091-770e-8822ef42a135.png)

こんな感じ．

* 上記はcordova-iosのエンジンとしてUIWebViewを利用している場合の対処法．WKWebViewを利用している場合にはうまく動作しない
* 上記ではStoryboardベースのスプラッシュ画像登録を行ったが，Legacyのスプラッシュ画像登録手法を使う際には，cordova-plugin-splashscreenのバージョンを5.0.2以上に上げる必要があるらしい（未検証）．

    http://kimagureneet.hatenablog.com/entry/2018/03/20/115108
    https://issues.apache.org/jira/browse/CB-13737

最近Monacaテンプレートに改修があったようで，ここで書いたiPhone X対応はすでになされているかも．

## [Android]「戻る」ボタンを無効にする

一部の画面で，Androidの「戻る」ボタン（ハードウェアボタン）を動作させないようにしたかった．

動作を完全に無効にし，押しても何も起こらない状態にするためには，Onsen UIの各UIコンポーネントのデフォルト動作と，Cordovaのデフォルト動作（`backbutton`イベントに対するデフォルト動作に相当？）の両方を無効にする必要がある．

https://ja.onsen.io/v2/guide/cordova.html#norubotan
https://cordova.apache.org/docs/en/7.x/cordova/events/events.html#backbutton

```javascript
const none = () => {}; // 「何もしない」関数

// 「戻る」ボタンを無効化
// Onsen UIが規定するデフォルト動作を無効化
ons.disableDeviceBackButtonHandler();
// Cordovaが規定するデフォルト動作を無効化
document.addEventListener(
    'backbutton',
    none
);

// 「戻る」ボタンを有効化
// Onsen UIが規定するデフォルト動作を有効化
ons.enableDeviceBackButtonHandler();
// Cordovaが規定するデフォルト動作を有効化
document.removeEventListener(
    'backbutton',
    none
);
```

# Angular関連

自分は下記のテンプレートをよく使っている．
https://github.com/monaca-templates/onsenui-v2-angular2-minimum

## devicereadyイベントを待つようにしたい

```main.ts
...

platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));
});
```

このテンプレートをそのまま使うと，Cordovaの`deviceready`イベントを待たずにAngularのロジックに入ってしまうので，プラグインを実行する箇所でときおり失敗してしまうようになる．

```main.ts
ons.ready(() => {
    platformBrowserDynamic()
        .bootstrapModule(AppModule)
        .catch(err => console.error(err));
});
```

のように，Onsen UIの`ons.ready()`メソッドを使うとよい．

https://ja.onsen.io/v2/guide/cordova.html#devicereadyibentonisuru

https://medium.com/@EliaPalme/how-to-wrap-an-angular-app-with-apache-cordova-909024a25d79

のように，`deviceready`イベントを直接待ってもよいが，`ons.ready()`メソッドを使う利点としては，Onsen UI側の初期化も一緒に待ってくれることがある．

欠点として，この記述を入れるとアプリの起動に少し待ちが発生するようなので，そこはトレードオフを見ながら．

## CSSのカプセル化を有効化したい

いずれ書く．

## 環境別にビルドを分けたい

いずれ書く．

## Windowオブジェクトを注入したい

いずれ書く．

https://medium.com/@EliaPalme/how-to-wrap-an-angular-app-with-apache-cordova-909024a25d79

## Cordovaプラグインが返すコールバック関数内で変更検知を有効にする

Angularには，Component（ロジック）内で変更した変数をビュー側に反映するための「変更検知」と呼ばれる機能がある．

（もう少し検証が必要だが，）一部のCordovaプラグインは，プラグインが提供するメソッドの戻り値としてコールバックやPromiseを返すが，このコールバック関数/Promiseが呼ばれた後の処理（`Promise.then()`内に記述する処理）について,
AngularのZone（`NgZone`）の外部で行われており，変更検知の対象とならない場合がある．

これを回避するためには，変更検知を明示的に行う必要がある．

https://qiita.com/maechabin/items/a10811bbb470bc490b80

上記記事のように，変更検知の対象としたい部分を`NgZone.run()`のブロックで囲うとよい．

ちなみに，処理がAngularから見て変更検知の対象となっているか調べたい場合には，`NgZone.isInAngularZone()`を使うことができる．

https://angular.io/api/core/NgZone#isinangularzone
https://stackoverflow.com/questions/38177621/how-to-check-whether-the-code-is-in-ngzone

# 開発/テスト/デバッグ関連

## ビルド環境

MonacaにはCloud IDE，Localkit，CLIの3つのビルド環境が存在するが，おすすめはLocalkit．
理由として，

* ローカル環境で開発できる
    * 好きなIDEが使える
    * Gitとの連携も容易
* GUIでCloudへのアップロードができる
* プレビューも比較的かんたんに確認できる
* （Cloud IDEに不可解な挙動が多い）

がある．

小さい変更なら，Cloud IDEの方がさくっと編集/反映できていい時もある．

## デバッグの流れ

Monacaデバッガーを使ってさっくり挙動を確認し，必要に応じてテスト/配布用のバイナリをビルドしてDeployGateなどの配布サービスにアップロードするのが基本の流れ．

MonacaにはUIのプレビュー機能も存在するが，こちらの機能ではCordovaプラグインが無効化されてしまうため，開発が進んでいくとほぼ使いものにならなくなってしまう．複数人での開発であれば，Cordovaプラグインの導入とUIの開発は分けて進めるとやりやすいと思う．

* UI（特にCSSまわり）の開発をする際は，MonacaデバッガーのUSBデバッグかLocalkitのペアリングを使うと，Chrome/SafariのDeveloper Consoleが利用できて非常に便利．
* ロジックのデバッグをする際はLocalkitでトランスパイルしてCloudにアップロード．実機のMonacaデバッガーでプロジェクトを更新すると変更が反映される

## Cordovaで生成されたプロジェクトを調べる

個人的な所感として，Cordovaを使っていて一番ハマるのは，Cordovaプロジェクトがネイティブ側のプロジェクトにコンパイルされる際に，必要な設定値（config.xmlやフックスクリプトによる各プロジェクトの設定ファイルへの変更）が反映されておらず，ビルドに失敗，あるいは起動しても動作がおかしくなる場合．
設定値をちゃんと確認することでこの手の問題をデバッグすることが可能だが，Monacaを使うとネイティブ側のプロジェクトへのコンパイルの部分がビルドシステムによって隠蔽されてしまうので，かえって問題がわからなくなってしまう場合がある．

Android: https://cordova.apache.org/docs/en/7.x/guide/platforms/android/index.html
iOS: https://cordova.apache.org/docs/en/7.x/guide/platforms/ios/index.html

上記の方法を使えば，ローカル環境でCordovaのビルドができる．

ハマってどうしようもなくなった場合は，

Androidの場合，

1. 対象のconfig.xmlのエントリやCordovaプラグインが設定している設定項目を調べる
2. platforms/android/build.gradleをAndroid Studioで開く
3. build.gradleやAndroidManifest.xmlを確認してみる

iOSの場合

1. 対象のconfig.xmlのエントリやCordovaプラグインが設定している設定項目を調べる
2. platforms/ios/${PROJECT_NAME}.xcworkspaceをXcodeで開く
3. Info.plistや${PROJECT_NAME}.entitlements，Podfileなどを確認してみる

を試してみると問題が解決するかもしれない．

## バイナリを調べる

上記の方法だが，正直に言って結構面倒．

もっと簡易的にやる方法として，生成されたアプリのバイナリを解析することで設定値を取得することもできる．

Androidの場合は，生成された.apkファイルに対してAndroid Studio内の「Build」->「Analyze APK」からAPK Analyzerを使うことで，AndroidManifest.xmlなどの確認ができる．

https://developer.android.com/studio/build/apk-analyzer

iOSの場合は，生成された.ipaファイルに対して`ipa_analyzer`を使うことで，Provisioning ProfileやEntitilementsファイル，Info.plistなどの設定値が確認できる．

https://github.com/bitrise-io/ipa_analyzer
https://qiita.com/tarappo/items/9600677bd02eb8558a49

# できなかったこと

今回のプロジェクトで導入を考えたものの実現できなかったことを記す．
（もし情報があったら教えていただきたいです）

## Firebase SDK + Crashlyticsの導入

Crashlyticsは，スマートフォンアプリ向けのクラッシュレポートサービス．
アプリに組み込むことで，アプリのクラッシュの情報を収集し，その時のスタックトレースや直前のユーザの行動などをダッシュボード上で運用者が確認できるようにする．
2017年にGoogleがTwitterから買収し，現在はFirebaseの一部として稼働している．

https://firebase.google.com/docs/crashlytics?hl=ja
https://japan.cnet.com/article/35095194/

現状，Twitter傘下の時代（Fabricと呼ばれていた）のダッシュボードからFirebaseへの過渡期らしく，既存のアプリについては，2020年3月末までにFabricダッシュボードからFirebase Consoleへの移行を行う必要があるらしい．

https://fabric.io/firebase_migration

これについて，現状のOSSのCordovaプラグインは，

1. Firebase Consoleでの稼働を意識し，credentialについても（現在Firebase側が推奨している）Firebaseのものを使っているもの

    https://github.com/ReallySmallSoftware/cordova-plugin-firebase-crashlytics
    https://github.com/arnesson/cordova-plugin-firebase
    
2. Fabric時代のcredentialを使うもの

    https://github.com/sarriaroman/FabricPlugin

の2種類に分かれている．

今後のことを考えると，できれば1.のプラグインを使いたいが，（過渡期で情報が錯綜していることもあってか）自分の手元の環境ではどれも動作させられなかった．

回避策として，Fabricダッシュボードを利用し，2.のプラグインを利用している．
FabricダッシュボードからFirebase Consoleへのデータ移行は比較的容易にできるので，現状これで特に困っていることはない．

## [iOS]スクロールの挙動がときおりおかしい

いずれ書く．

## [iOS]Today Widgetを導入する

いずれ書く．

https://github.com/DavidStrausz/cordova-plugin-today-widget

## [iOS]ons-pull-hookを導入する

Onsen UIには，`ons-pull-hook`という，Pull To Refresh（下にスワイプして更新）を実現するUIコンポーネントが存在する．

https://ja.onsen.io/v2/api/js/ons-pull-hook.html

（自分の環境だけなのかもしれないが，）cordova-ios/UIWebView環境でons-pull-hookを導入し，ons-pageの`content`部分をスクロールできるような状態にしておくと（例えば大量にリスト要素をおいておくとか），ユーザがons-pull-hookを引いた時点で上下にガクつくような動きをする．

このままだと使いものにならないので，とりあえずバグ報告してみた↓

https://github.com/OnsenUI/OnsenUI/issues/2653

ちなみに，`cordova-plugin-wkwebview-engine`を用いてcordova-ios自体をWKWebView化すると事象が消失することを確認している．

https://github.com/apache/cordova-plugin-wkwebview-engine

# 感想

Cordovaつらい．
https://note.mu/rdlabo/n/n1e8e71228e21