読者です 読者をやめる 読者になる 読者になる

ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Dropwizard + JDBI で SQL オブジェクトの返り値に Optional を使うときには SingleValueResult アノテーションが必要

Java ライブラリ Dropwizard JDBI

DropwizardSQL ライブラリ JDBI を使うときのおはなし。

OptionalContainerFactory

dropwizard-jdbi ライブラリは OptionalContainerFactory クラスを提供してくれていて、JDBI の SQL オブジェクトで返り値に Java 8 で導入された Optional 指定することができます。

ちなみに普通に dropwizard-jdbi の DBIFactory#build メソッドを使うと自動的に OptionalContainerFactoryDBI に登録してくれるので、自分で登録する必要はありません。 (Dropwizard 1.0.5 で確認。)

SingleValueResult アノテーションが必要 (単に Optional を指定するだけでは動かない)

SQL オブジェクトのクラス定義で返り値に Optional を指定すればそれだけで動くのかと思いきや、実はそんなことはありません。 Optional を返すメソッドに SingleValueResult アノテーションを付ける必要があります。

import info.vividcode.app.web.example.dropwizard.domain.Person;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.customizers.SingleValueResult;

import java.util.Optional;

public interface PersonDao {

    @SqlQuery("SELECT * FROM person LIMIT 1")
    @SingleValueResult(Person.class)
    Optional<Person> findOne();

}

ここら辺のドキュメントがないので、ソースコードを読んで確認しました。

Optional を使う場合に限らず、Iterable じゃなくてコンテナを使いたい場合は SingleValueResult アノテーションを付ける必要がありそうですね。

関連ページ

MySQL Connector/J 5.1 系では useLegacyDatetimeCode=false にしよう

Java MySQL ライブラリ

JDBCMySQL に接続するときに使用する MySQL Connector/J (mysql:mysql-connector-java) の話。 サーバー・クライアントのタイムゾーン設定が違っている場合にどう対応するのがいいか。

結論

  • MySQL Connector/J 6 (まだ開発版だけど) 以降は自動でやってくれるので気にする必要はない。
  • MySQL Connector/J 5.1 では URL に useLegacyDatetimeCode=false を入れて、時刻周りの新しい処理が動くようにしろ。
    • 新しい処理では、タイムゾーンの変換を一貫性をもってやってくれるようになる。
    • 『Use code for DATE/TIME/DATETIME/TIMESTAMP handling in result sets and statements that consistently handles time zone conversions from client to server and back again』
    • 参考 : 5.1 Driver/Datasource Class Names, URL Syntax and Configuration Properties for Connector/J
    • 5.1 系ではデフォルトでは互換のために新しい処理は動かないようになっているので、明示的に新しい処理を使うように URL で指定する必要がある。
    • バージョン 5.1.6 で導入された機能なので、それより古いものでは使えない。

問題

そもそもどういう問題に遭遇したのか。

MySQL Connector/J とタイムゾーン

おわり

タイムゾーンはライブラリ側がちゃんと面倒見てくれるだろう、と思って気にしなかったら、環境を変えて Java アプリケーションと MySQL サーバーのタイムゾーン設定がずれたときにいきなり想定しない動作になったりするので気を付けましょう。

grunt-ts が .baseDir.ts ファイルを作るのを抑制する

TypeScript JavaScript ライブラリ 開発環境

Grunt を使っていて TypeScript のビルドを行うタスクを定義する際には grunt-ts を使うことが多いでしょう。 (grunt-typescript もあるけど。) grunt-ts で困ったことがあったので書いておきます。

outDir オプションを使うと .baseDir.ts ファイルが作られる問題

grunt-ts を使って tsc コマンドの --outDir オプション指定を行うには outDir プロパティが使えるのですが、この値を指定すると、何故か 「.baseDir.ts」 という名前のファイルがソースディレクトリの方に生成されてしまいます。

このファイルは何かというと、TypeScript 1.5 で --rootDir オプションが導入される前に grunt-ts 独自に --rootDir 相当のことをするために導入されたものです。 grunt-ts のドキュメントを読む限り、baseDir オプションを使っておらず、fast コンパイルが無効になっている場合には 「.baseDir.ts」 ファイルは作られないはずです。 しかしながら、grunt-ts 5.5.1 で試したところ、baseDir オプションを使わず fast オプションに "never" を指定しても outDir オプションを指定すると 「.baseDir.ts」 ファイルが作成されてしまいました。 個人的にはバグっぽいなーと思っています *1。 一応 pull request を投げておきました。

ちなみに以下のような Gruntfile.js (一部) で動作確認しました。

grunt.initConfig({
  ts: {
    default: {
      src: ["src/ts/**/*.ts"],
      outDir: "build/ts"
      options: {
        fast: "never"
      }
    }
  }
});

回避方法

現在 (バージョン 5.5.1) の grunt-ts に .baseDir.ts ファイルを作らせないようにする方法はいくつかあります。

outDir オプションではなく additionalFlags--outDir オプションを指定する

grunt-ts は outDir オプションを見て 「.baseDir.ts」 ファイルを生成するかどうか決めているので、additionalFlags の方で --outDir を指定すると 「.baseDir.ts」 ファイルは生成されません。

grunt.initConfig({
  ts: {
    default: {
      src: ["src/ts/**/*.ts"],
      options: {
        additionalFlags: "--outDir build/ts"
      }
    }
  }
});
tsconfig.json ファイルを指定する

tsconfig.json の中で outDir オプションを指定し、grunt-ts には tsconfig プロパティで tsconfig.json のパスを指定教えるだけにするという方法もあります。 ただし、普通に指定するだけでは tsconfig.json を解析して grunt-ts が余計なことをするっぽい (tsconfig.json 側で outDir オプションを指定していると、grunt-ts が .baseDir.ts ファイルを作ってしまう) ので、passThrough オプションを有効にして、grunt-ts が余計なことをしないようにする必要があります。

grunt.initConfig({
  ts: {
    default: {
      tsconfig: {
        tsconfig: "src/ts/tsconfig.json", // このファイルの中でコンパイル対象のファイル群や `outDir` 指定を行う
        passThrough: true
      }
    }
  }
});

おわり

こんなしょうもないことをやってるのは無駄だと思うし grunt-ts はオススメしません。

関連ページ

*1:ドキュメントの記述から察するに仕様ではなさそうだし、コードの実装も微妙に変なので

UWP アプリ 「みお×ぽん」 のバージョン 1.1.0 をリリースしました

UWP アプリ Release notes

2016 年 10 月 1 日、IIJmio の 「IIJmio 高速モバイル / D サービス」 が au の回線にも対応して 「IIJmioバイルサービス」 に改称されました。

それに合わせて、「IIJmioバイルサービス」 のクーポン切り替えを行うことができる UWP アプリ 「みお×ぽん」 をバージョンアップしました。

www.microsoft.com

文言変更や一部デザイン変更、バグ修正のみであんまり大きな変更はしていないのですが、ご利用くださっている方はバージョンアップしてくださいませ。

変更内容

  • IIJmio 高速モバイル / D サービス」 という文言を 「IIJmioバイルサービス」 に変更。
  • 画面幅が小さい場合には、閉じた状態のナビゲーションウィンドウを完全に非表示にするように変更。
  • UWP アプリのライフサイクルを適切に扱えていなかった問題を修正。 *1

ちなみに公式アプリもあるよ

「みお×ぽん」 の最初のリリース時には IIJ による公式の UWP アプリはなかったのですが、2016 年 6 月ごろに公式アプリがリリースされました。

やったぜ!

*1:昔の MS のサンプルプロジェクト通りの実装だとだめっぽい。 これに関してはまた書こうと思う。

レスポンシブデザインのために resize イベントを使うのはやめて matchMedia メソッドを使おう

JavaScript

レスポンシブデザインのために CSS メディアクエリを使うことが多いと思います。 CSS 側だけで完結したらいいのですが、JavaScript 側でも画面サイズの変更を検知したかったり、画面サイズ以外のメディアクエリ相当のことをしたくなったりすることはありますよね。

画面サイズの変更自体は window に発生する resize イベント (window.onresize イベントハンドラ) で検知できますが、CSS メディアクエリとこれを組み合わせてレスポンシブ対応しようとすると以下の問題がでてきます。

  • ウィンドウサイズ変更時に resize イベントが高頻度で発生するので、resize イベントのリスナでコストのかかる処理を行うのはよくないとされる。 (Throttling することが推奨される。)
  • CSS メディアクエリと完全に対応するものではないので、CSS 側との対応を取りづらい。

上の方はまあ throttling すればいいのですが、下の問題はどうしようもないですね。

window.matchMedia メソッドと MediaQueryList

そこで別の方法としておすすめしたいのが window.matchMedia メソッドとその返り値の MediaQueryList オブジェクトです。 標準化についてはまだ完了しておらず、CSSOM View Module で作業されているようです。 とはいえ最近のブラウザだとどのブラウザでも使えるみたいなので、実用的に使っていける状況になっているかと思います。 (IE 9 とか少し古めの Android のブラウザとかでは使えないので、そこら辺のサポートが必要ならまだ使えませんが><)

window.matchMedia メソッドの引数としてメディアクエリのリスト (media query list: メディアクエリをカンマ区切りで繋げたもの) を渡すと、それらのメディアクエリのリストを表す MediaQueryList オブジェクトが返されます。 MediaQueryList#matches プロパティを使うことで、リスト中のメディアクエリのうち少なくとも 1 つ以上が真になっているか、もしくはすべてが偽であるかを判別できます。

また、MediaQueryList#addListenr メソッドでイベントリスナを設定することで、matches の値の変化を検知することができます。

// TypeScript です。

// 縦 600px 以下、または横 600px 以下の場合に matches state が真になるメディアクエリリスト。
var mql = window.matchMedia("(max-width: 600px), (max-height: 600px)");
// メディアクエリリストの matches state に応じた処理を行う関数。
function handleMediaQueryListMatchesState(matches: boolean) {
    if (matches) {
        // メディアクエリリストの matches state が真の場合の処理。
    } else {
        // 偽の場合の処理。
    }
}
// イベントリスナを設定して matches state の変化を検知。
mql.addListener((evt) => handleMediaQueryListMatchesState(evt.matches));
// 初期化。
handleMediaQueryListMatchesState(mql.matches);

便利ですね。 非対応ブラウザを切っていいようでしたらどんどん使っていきましょう。

歴史的経緯?

ところで MediaQueryListaddEventListener メソッドを持っているはずなのに、それとは別に addListener メソッドも持っていてどうなってるんだろう、と思いますよね。 私も思いました。 どうやら昔は MediaQueryList は独自のコールバックの仕組みを使っていて、addEventListener を持っていなかったようです。 また、コールバックメソッドに引数として渡される値も MediaQueryListEvent ではなく、MediaQueryList オブジェクトがそのまま渡されていたようです。

Note: This specification initially had a custom callback mechanism with addListener() and removeListener(), and the callback was invoked with the associated media query list as argument. Now the normal event mechanism is used instead. For backwards compatibility, the addListener() and removeListener() methods are basically aliases for addEventListener() and removeEventListener(), respectively, and the change event masquerades as a MediaQueryList.

CSSOM View Module, 4.2. The MediaQueryList Interface

実際に試したところ、Firefox 49.0.1 や Edge 38.14393.0.0 では古い挙動になっていました。 Chrome 53.0.2785.116m は最新の CSSOM View Module にあった実装になっていました。 Firefox や Edge の実装はまだ最新の CSSOM View Module にあった実装になっていないので、しばらくは addListener メソッドを使っていくようにするのが良さそうです。

他の方法

CSS 側でメディアクエリを使って特定要素のプロパティを変更するなどして、JS 側からはそのプロパティを見ることでどのメディアクエリが有効になってるのか検査するのが今のところは安定、という話も。 IE 9 や古めの Android 端末をサポートするならそういう方法が良さそうです。 (ということですよね? 他の理由があるなら教えてください!)

レスポンシブデザインのために resize イベントを使うのはやめて matchMedia メソッドを使おう - ひだまりソケットは壊れない

今はまだ画面サイズ検知用の要素作って、font-familyとかをメディアクエリで変更するのが一番楽かな。font-family: "sp";とか。resizeイベント側はfont名を見るだけ。ただイベントで変化を検知できるのはいいなぁ

2016/10/02 14:57


参考