ReactNative事始め
必要に迫られてReactNativeを最近触っています。慣れるとサクサク画面が作れるので,ちょっと可能性を感じています……! SwiftやKotlinといったNative言語での開発も愉しいですが,こういった高速開発がしやすいツールも使えると活躍の幅が広がるのではないかと思っています。プロトタイピングの時なんかに使ってもいいんじゃないでしょうか。※本開発でも使える品質だと思いますが。
ReactNativeとは?
ReactNativeは,facebookが開発しているオープンソース(MIT)のスマートデバイスアプリ開発ツールです。SPA開発ツールであるReact.jsの知識をベースにして,iOSやAndroidのNativeアプリケーションをワンソースで開発可能です。facebookアプリやInstagramアプリ,AirBnBアプリといった有名アプリの開発でも採用されており,十分実用に耐えうる品質を備えたツールだと思います。そんなReactNativeの特徴は以下です。
- Reactの知識をベースに開発できる
- NativeのUIコンポーネントが利用できる
- ホットリロードで高速開発できる
(1) Reactの知識をベースに開発できる
Reactの特徴として「Learn once write anyware」という言葉がよく使われます。Javaも昔似たようなことを言われてましたね。一度流儀を理解すれば,それを他の開発(スマホアプリやVRアプリ)でもいい感じに活用できる……というのがコンセプトで,ReactNativeもその流儀に則ったツールと言えます。
- ReactNative: Reactの流儀でNativeアプリが作れる
- ReactVR: Reactの流儀でWebVRアプリが作れる
- ReactNative for Web: Reactの流儀で抽象化された開発方式でWebアプリが作れる
Reactを触ったことがあるならば,少ない学習コストで開発をスタート出来ることは間違いないです。JavaScriptの知識ではなく,Reactの知識が前提になる点は注意です。
(2) NativeのUIコンポーネントが利用できる
ReactNativeでは,Apache CordovaのようなHybridアプリ開発とは違い,画面描画をNativeの技術で行います。具体的に言うと<Button>
タグを使った場合,iOSではUIButton
クラスを使って画面描画されますし,AndroidではButton
クラスが使われます。アプリ側が勝手に作った非標準のUIコンポーネントではなく,利用者が慣れ親しんだ標準UIコンポーネントで画面を作れる――パフォーマンスやユーザビリティの面でも大きなメリットがある方式です。
(3) ホットリロードで高速開発できる
Nativeアプリを開発する際に困るのがビルド時間が長くなることです。ちょっとした修正でもリビルドで数分待たされるとヤル気が下がるのではないでしょうか。ReactNativeは,ホットリロードという仕組みを採用しており,JavaScriptファイルの差し替えだけでアプリが更新されるので,サクサク高速にアプリ開発を進められます。
ReactNativeの始め方
cliコマンドから雛形プロジェクトを生成出来るので活用しましょう。
npm install -g react-native-cli
他のcliツールとしてcreate-react-native-app
も在るのですが,Nativeコードを含むReactNative用のOSSライブラリの導入が面倒だったり細やかな変更が難しい点からreact-native-cli
の方を推奨したいです。※create-react-native-app
だと変更頻度が低いファイルを隠蔽した形で雛形が生成されるため,初心者が混乱しにくいというメリットも存在します。
react-native init MyProject cd MyProject react-native run-ios
上記のコマンドで雛形アプリが起動します。
react-native run-android
EcmaScript5でのクラス実現パターン
はじめに……そして,結論
調べた結果,以下のように書くのが一番ベターっぽい気がしてきました。
var MyClass = function() { //Privateな属性 const privateValue = "private"; const privateMethod = function() { console.log("private"); } //Publicな属性 const F = function() {}; F.prototype.publicMethod = function() { privateMethod(); console.log("hogehoge"); } return new F(); }
Optional型活用のすゝめ
はじめに
Streamの話を↑でしましたが,コードレビューで指摘する内容として「Optional型を上手く使いましょう」という話もよくしています。Optional型もJava8から登場した比較的新しい文法になりますが,nullを扱う場合に便利になるので積極的に活用していきたいところです。
Optional型=nullかもしれない値
Optional型はJava8から追加された新しいクラスです。どういうケースで使うといえば,nullを返却する可能性があるメソッドの戻り値型として採用すると良いでしょう。まず,従来の文法で説明してみます。
private MyEntity convertToMyEntity(String name) { if (name.equals("Apple")) { return new MyEntity("name"); } else { return null; } }
引数で与えられる文字列が"Apple"の時以外はnullを返します。このメソッドを使う側は,処理結果としてnullが返ってくるかどうかをどうやって判定するのでしょうか? 呼び出し先のソースコードがあるならば,そちらを見れば良いでしょうが対象クラス・メソッドがjarファイルになっているケースもあります。なにより毎回ソースコードを当たらないとnullが返るかどうか分からないというのは面倒です。そういう時に使ってほしいのがOptional型なのです。
private Optional<MyEntity> convertToMyEntity(String name) { if (name.equals("Apple")) { return Optional.of(new MyEntity("name")); } else { return Optional.empty(); } }
こんな感じで戻り値型をOptional型でラッピングして返却します。”これ,なんの旨みがあるの?”と思いますよね。Optional型のミソはラッピングされた値を取り出すところにあります。Optional型から元のMyEntity型を取得するコードは以下のようになります。
Optional<MyEntity> entity = convertToMyEntity("Apple"); entity.ifPresent(e -> { //e=MyEntity });
値を取り出す際にnullチェックを強制出来るという点が最大の特徴です。このルールをチームメンバーで共有しておけば,少なくとも自前で作成したコード部分については
- Optional型=nullが返ってくる可能性がある
- 非Optional型=nullは返ってこない
といった方針でコーディングが出来るのではないでしょうか。※getメソッドでチェックすることなく強制的に中身を取り出すことも出来ますが,例外が飛ぶので禁止しましょう。コードレビューでの注視ポイントだと思います。
個人的には最初は↑の箇条書きの方針で採用していけば良いと考えていますが,ある程度Optional型に慣れてきたらもう一検討して欲しいです。最初に述べた通り,Optional型は相手にnullチェックを強要出来ます。逆に言うとnullでも構わないようなケースは別にOptional型である必要はないでしょう。nullを扱うためにいちいちアンラップするのも面倒ですから。検査例外と非検査例外……みたいですね。
戻り値以外でOptional型を使うのはNG
「Optional型,便利じゃん!」ってなってきましたか? 便利なOptional型ですが使う場所は限定しましょう。Optional型に慣れてきた頃にやりがちなミスですが,フィールドの型としてOptional型を使ったりメソッド引数としてOptional型を使うのは止めるべきです。
private Optional<String> option; private void doSomething(Optional<String> args) { //なんかの処理 }
理由は2点です。
- 再代入できる様式だと考慮すべきパターンが増える
- 引数でOptional型を与えるメリットが誰にもない
前者「再代入できる様式だと考慮すべきパターンが増える」はフィールド型として使う場合の話です。再代入可能なOptional型を定義するとそれを参照する側は
- Optional型オブジェクトは存在するが,中身が空(null)である
- Optional型オブジェクトは存在するが,中身が入っている
- Optional型オブジェクトが存在しない(=nullで代入された)
という3つのパターンを考慮しないといけません。これでは逆にデメリットが増えてしまいます。採用するのであればgetterメソッドの型だけをOptionalにする方が無難でしょう。
後者「引数でOptional型を与えるメリットが誰にもない」は説明が難しいのですが,メソッドを作る側の立場になって考えてみましょう。引数型がOptional型以外でも結局のところnullチェックって実装するのではないでしょうか? 非Optional型で来たからといって信用できないかなという気がしています。なので,個人的には引数にはOptional型を使わない方が良い?と思っています。基本,Optional型は受け取った場所(メソッド)で中身を精査して扱うというのが王道なのでしょう。
Oracleの中の人も「Optional型は戻り値だけで使ってねん」と言ってた気がします。
Streamを活用すると綺麗に書けるJavaパターン
はじめに
ここ数ヶ月くらいJavaのコードレビューをする機会が何回かありました。Javaは社内でも知っている人が多い言語ですが,歴史が長いだけあって昔々の文法を貫いてコード書く人が結構居ます。なかなか新しい要素を勉強し続けるのも辛いと思うのですが,やはり新しいものは良いものが多いので地道に布教活動を続けていきたいと思っています。今回は,特に指摘するケースが多いJava8から使えるようになったStream文に関する内容を整理してみます。
単純なループ
下のようなコード結構見かけませんか?
List<String> nameList = Arrays.asList("1st", "2nd", "3rd"); for (int i = 0; i < nameList.size(); i++) { fetchSomeInformation(nameList.get(i)); }
いちいちリストの件数を取ってループするのも直感的ではありません。拡張for文を使うのもGoodですが,Streamで置換するのも良い手段だと思います。
List<String> nameList = Arrays.asList("1st", "2nd", "3rd"); nameList.stream().forEach(n -> fetchSomeInformation(n));
ループ中の条件判定
前の例は単純過ぎたのでもう少し実践的なケースを……。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); nameList.forEach(n -> { if (n.length() == 5) { fetchSomeInformation(n); } });
リストの中身を判定して特定のケースだけで処理を行うコードです。よくやりますよね?こういう処理こそStreamで置換すると効果が大きいと思います。これを置き換えると……。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); nameList.stream() .filter(n -> n.length() == 5) .forEach(n -> fetchSomeInformation(n));
こんな感じになります。実行条件と実行内容がそれぞれfilterとforEachに分かれることで処理も見やすくなります。仮に条件が増えた場合も……。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); nameList.stream() .filter(n -> n.length() == 5) .filter(n -> n.startsWith("A")) .forEach(n -> fetchSomeInformation(n));
すっきり書けちゃいます。
リストの変換
リストでループを回しながら,独自のオブジェクトを作る下のようなコード……よくありますよね。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); List<MyEntity> entities = new ArrayList<>(); for(String name : nameList) { entities.add(new MyEntity(name)); }
こういうケースではmapメソッドが大活躍です。mapはStreamを別の形式に変換する機能を提供しています。
List<String> nameList = Arrays.asList("Apple", "Orange", "Pine"); List<MyEntity> entieies = nameList.stream() .map(n -> new MyEntity(n)) .collect(Collectors.toList());
かなりスッキリしたコードになりますね!
Token認証対応のAPNs ProviderをJavaで実装する
久しぶりのiOSネタです。iOSデバイス相手のPUSH通知を実現するApple Push Notification Services(APNs)ですが、JWT認証の方を使ったことがなかったので今回はそちらに挑戦してみようと思います。
おさらい
APNsサーバは、Providerとの交信方法を2種類提供しています。
「バイナリ Provider API」は、iOS黎明期からサポートされてきた昔ながらの交信方法です。APNsサーバとProviderサーバ間でソケット通信を行い、所定フォーマットのバイナリデータで通知を依頼します。自前で実装する場合、ペイロード(依頼内容)のバイナリ変換が面倒くさい印象があります。AppleでもこれからProviderサーバを実装するのであれば、バイナリProvider APIは非推奨……と言っています。その内、サポート切られるのでは?
「APNs Provider API」は、WWDC2015で発表された新しい交信方法です。HTTPのGET/POSTメソッドでAPNsサーバに対して通知依頼を出すことが出来るので、非常に簡便にプッシュ通知を実現できるようになりました。また、通知失敗時もHTTPリクエストのレスポンスで詳細な原因が返却されますので、再通知やデバッグが捗る点も魅力的ですね。
交信方法やエラーハンドリングの簡便さ以外に「APNs Provider API」の大きな特徴が、APNsサーバとの認証方式です。
APNsサーバとの認証方式
「APNs Provider API」は以下の2種類の認証方式をサポートしています。
- 証明書認証
- JWT認証
「証明書認証」は、Member Centerから発行したAPNs用SSL証明書を使った認証です。証明書の有効期限が1年で失効されるため、定期的に証明書を再生成して差し替えないと通知が送れなくなるというデメリットがあります。また、証明書はアプリ単位に作成するので管理も煩雑になりがちです。受託開発案件をやっている人は、結構面倒くさいのではないでしょうか?保守料を取れるという見方もありますが。ちなみに、バイナリProvider APIは、証明書認証だけしかサポートしていません。
「JWT認証」は、Member Centerから発行したAPNs Auth Keyを使った認証です。APNs Auth Key自身には有効期限が存在せず、このAPNs Auth Keyを使ってAPNsサーバに認証トークンを要求する点が大きな特徴であり、これらの認証トークン取得手続きは全てProviderサーバのプログラムで行う必要があります。その代わり、1度処理を実装してしまえばAPNs Auth Keyを手動で失効させない限り、ずっとプッシュ通知を送り続けられる環境が実現できます。証明書認証と異なり、通知先のアプリに依存する要素が少ない点も嬉しいです。
JavaでProviderサーバ(JWT認証)を実装する
本題です。フル自前実装でも良いのですが、OSSを活用しましょう。
(1) pom.xmlの編集
Mavenに登録されているので、Providerサーバ用プロジェクトのpom.xmlに依存関係を追記します。
<dependency> <groupId>com.relayrides</groupId> <artifactId>pushy</artifactId> <version>0.9.3</version> </dependency>
あと、ポイントとしてはログ出力用のライブラリも追加した方が無難です。通知に失敗したときの詳細挙動がログから追えるからです。Java標準のエラー出力だと原因調査が難しいので、ぜひ設定しましょう。
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
(2) 通知処理の実装
あとは簡単です。
try { // 通知クライアントを作成 final ApnsClient apnsClient = new ApnsClientBuilder().build(); apnsClient.registerSigningKey(new File("[APNs Auth Keyが置かれた場所までのファイルパス]"), "[TeamId]", "[APNs Auth Keyの認証コード]", "[通知先アプリのAppID]"); // ペイロードを作成 ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder(); payloadBuilder.setAlertBody("hoge"); String payload = payloadBuilder.buildWithDefaultMaximumLength(); // 開発用APNsサーバに接続 Future<Void> connectFuture = apnsClient.connect(ApnsClient.DEVELOPMENT_APNS_HOST); // 接続完了まで同期で待つ connectFuture.get(); // 通知オブジェクトを作成 SimpleApnsPushNotification pn = new SimpleApnsPushNotification("[デバイスToken]", "[通知先アプリのAppID]", payload); // 通知実行 apnsClient.sendNotification(pn); // APNsサーバから切断 apnsClient.disconnect(); } catch (SSLException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
BigCalendarが表示されないときの覚書
最近,仕事の都合でReact.jsを触っています。規模が大きいアプリを作る際にコンポーネント指向の恩恵を感じており,(コンポーネント単位で)品質を維持しながら開発を進められる点が魅力的ですね。Reduxなどの状態管理ライブラリも触りたいのですが,今関わっているプロジェクトを何とか終えなければ......というところで,詰まった点を覚え書きしておきます。
1. Big Calendarがちゃんと表示できない!
React.jsの便利なUIライブラリに「Big Calendar」という部品があります。その名の通り,カレンダー(≒スケジューラ)を表示するためのOSSライブラリで,以下のようなカレンダーを簡単に表示できるようになります。
ただ,初めて使った際は↓のような感じで正しく表示されず,対応に困りました。CSSのリンクを外すと日付は表示されるので,原因はその辺りにありそうだな......と思いながら数刻が過ぎ。
2. サイズ指定が要る
解決しました。公式ページに以下の記述があります。
The default styles use height: 100% which means your container must set an explicit height (feel free to adjust the styles to suit your specific needs).
コンテナに高さ指定が要るということですね。
<div className="Main"> <BigCalendar events={this.state.events} culture='ja-Ja' defaultDate={new Date(2017, 1, 2)} className="Cal" /> </div>
.Main .Cal { height: 600px; }
出た......!
GradleでWebアプリ
今更ながらJavaのビルドツール「Gradle」を触ってみました。
Mavenに比べるとビルドスクリプトが手軽に書けるのが良いですね!
(pom.xmlの構文エラーとの戦いを経験している特に)
今回はEclipseのGradleプロジェクト形式で作成したプロジェクトを
Tomcat上で動かすところまでの手順を整理してみます。
手元の環境はこんな感じです。
1. Gradle(STS)プロジェクトを作る
予めEclipseにGradle(STS)プラグインをインストールしておきましょう。
ヘルプのEclipse Marketplaceから入手可能です!
「Java Quick start」でシンプルにプロジェクトを作成します。
2. Servlet-APIを入手する
今回は単純なJavaServletで動作を確認します。
build.gradeを編集し、Servlet-APIを依存ライブラリに追加します。
dependencies { compile group: 'commons-collections', name: 'commons-collections', version: '3.2' compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' testCompile group: 'junit', name: 'junit', version: '4.+' }
依存ライブラリを追加したらプロジェクトを選択して
「Refresh Dependencies」を実行すれば依存ライブラリが入手可能です。
3. プロジェクトをWTPに変換する
GradleプロジェクトのままではTomcatにデプロイできないので
WTPプロジェクトに変換したいと思います。
apply plugin: 'war' apply plugin: 'eclipse-wtp'
「war」プラグインと「eclipse-wtp」プラグインを追加で導入します。
上記の通りbuild.gradeファイルの先頭に記述を追加します。
導入後に
gradle eclipse
を実行すればWTPプロジェクトに変換されます。
4. Servletを実装する
ここは普通にServletを実装します。
@WebServlet(name="MyServlet", urlPatterns={"/test"}) public class MyServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter writer = resp.getWriter(); writer.append("<html><body><h1>Hoge</h1></body></html>"); writer.flush(); } }
5. 実行する from Eclipse
実行→Run on Serverで起動します。
http://localhost:8080/test で画面が表示されます。
6. WARファイルを作成する
gradle buildコマンドを実行すればwarファイルも作成可能です。