スマートデバイスアプリ開発のあれやこれや

Mint60を作ってみた

 ゆかりさんが販売されている自作キーボードのMint60を買いました。元々,職場ではMistelのBAROCCOを使っていたのですが「自宅でも分離型のキーボードが使いたい!」という思いから,電子工作初心者にも関わらず組み立てにチャレンジした次第です!

www.archisite.co.jp

 今回は電子工作初心者目線で,Mint60を組み立てた感想を書いてみます。結論を最初に述べると「電子工作初めてでも完成までたどり着けるよ!!」と初心者の方に伝えたいです。せっかくはんだごてを購入したので,キーボード以外のものも作ってみたいなと思うようになりました。

私の電子工作経験

  • 最後に半田付けしたのは中学生の頃
  • 電子工作の道具は今回初めて買った

使った機材

  • はんだごて
    • 温度調整機能付きのものを購入しました

白光 ダイヤル式温度制御はんだこて FX600

白光 ダイヤル式温度制御はんだこて FX600

  • こて先
    • 2Cサイズが重宝します

  • こて台
    • スチールウールではんだを掃除出来るのでコテ先の温度低下を防止できます

白光(HAKKO) こて台 633-01

白光(HAKKO) こて台 633-01

  • ハンダ
    • 片手でハンダを引き摺り出しながら使えるので便利でした

goot 電子工作用はんだ SD-63

goot 電子工作用はんだ SD-63

  • リードベンダー
    • ダイオードを大量に折り曲げる必要があったので購入しました
    • ジャスト間隔で針金を曲げられるのでダイオードの浮きを防止出来ます
    • 途中まで使い方を間違えていました。裏側の溝を使いましょう

電子工作初心者ゆえの困惑

  • はんだをどれだけの量盛っていいのか分からない……
  • 基盤を焦がさないか心配になる

はんだづけのポイント

  • はんだごての温度は370℃程度であれば焦げたりしない
  • コテ先(C2)の平らな部分ではんだを溶かす
    • そこから重力で滑らせるイメージを持つ(適量が自然と流れ落ちる)
    • ぷっくらするまではんだを垂らさなくてOK
  • こまめにコテ先のはんだ付着を落とす
    • ↑で紹介したコテ台はコテ先を拭いても温度が下がらないので便利です

完成後に気付いた不具合

  • 右側の下段(N~?ぐらいまで)が反応しない
    • ケースと合体すると高確率で反応しない。ケース外すと復活。
  • 「Y」が反応しない

対策

  • 右側下段が反応しない問題
    • 他の方も書いておられますが,LEDテープとキースイッチの足の干渉でした
    • はみ出ているキースイッチの足をニッパーで切り落としたら直りました
  • 「Y」が反応しない
    • ダイオードの設置が一箇所漏れてる……
    • キースイッチも全部はんだづけ終わっていたので絶望的な気分になりました
    • ダメ元で説明ページとは逆方向から基盤にダイオードをはんだづけしたら……成功!

使用感

  • 静音赤軸を初めて使ってみたが,その名の通り静かで素晴らしい
  • スタビライザが内蔵されているキーはちょっとだけ音が気になる
  • キー配置が普段使っているものと違うのでなれるまで時間かかりそう

AsciiDocでテーブルの中に画像やコードを表示したい!

 AsciiDocが便利なので近頃ちょくちょく活用しています。もうWordやExcelには戻りたくないほど感動的な体験で,「今後作る設計書は全てAsciiDocで作ってやろう……!」と堅く心に誓ってしまうほどです。どの部分に惹かれるかは人それぞれですが,私にとっては以下の特徴が魅力的に感じました。

  • 表紙や目次を持ったフォーマルな文書作成をサポートする機能が多い
  • 下手にWordで頑張るより小奇麗なファイルが生成される
  • ドキュメント作成時のデザイン崩壊事故が起きにくい
    (デザイン定義が別ファイルになるので基本触らない)
  • ソースコードのSyntaxHighlightが効く

 そんなわけで便利なAsciiDocなのですが,ちょっと困ったことがあったので今日はその紹介を……。

テーブルの中に画像を表示したい!

 要するに↓みたいなことをやりたかったのです。

f:id:cross-xross:20180616080352p:plain

 でも普通にAsciiDocを書くと

|===
|1-1|2-1

|image::sample.jpg[width="100", align="center"]
|2-2

|1-3
|2-3
|===

f:id:cross-xross:20180616080744p:plain

 こんな感じで残念な表示をされてしまいます。

対策は……?

 意外と簡単に対策できました。

|===
|1-1|2-1

a|image::sample.jpg[width="100", align="center"]
|2-2

|1-3
|2-3
|===

 内部でAsciiDocの文法を解釈させたいセルの直前に「a」を追加します。

f:id:cross-xross:20180616080352p:plain

 出た!

 画像だけでなくSyntaxHighlightも効くので↓みたいなことも可能です。

f:id:cross-xross:20180616081535p:plain

別解

 テーブルのヘッダ定義部分をイジっても同じことが出来ます。

[cols="1a,1"]
|===
|1-1|2-1

|image::sample.jpg[width="100", align="center"]
|2-2

|1-3
|2-3
|===

 特定列だけ常にAsciiDocを解釈して欲しいなら,こちらの方がラクですね!

ReactNative事始め

 必要に迫られてReactNativeを最近触っています。慣れるとサクサク画面が作れるので,ちょっと可能性を感じています……! SwiftやKotlinといったNative言語での開発も愉しいですが,こういった高速開発がしやすいツールも使えると活躍の幅が広がるのではないかと思っています。プロトタイピングの時なんかに使ってもいいんじゃないでしょうか。※本開発でも使える品質だと思いますが。

ReactNativeとは?

 ReactNativeは,facebookが開発しているオープンソース(MIT)のスマートデバイスアプリ開発ツールです。SPA開発ツールであるReact.jsの知識をベースにして,iOSAndroidの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ファイルの差し替えだけでアプリが更新されるので,サクサク高速にアプリ開発を進められます。

f:id:cross-xross:20180606215811p:plain

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

 Androidの場合は先に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型活用のすゝめ

はじめに

niwaka.hateblo.jp

 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型は戻り値だけで使ってねん」と言ってた気がします。

togetter.com

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
  • APNs Provider API

 「バイナリ 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を活用しましょう。

github.com

(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();
}