Androidアプリ公開しました!

 かつてより作ってみたいな〜と思っていたAndroidアプリをGooglePlayに製品版を公開しました!

play.google.com

 WebViewで作られていたソシャゲを担当していた時、ネイティブの機能を使ってあれやりたいこれやりたいと思ったり、このJavascriptはどういう仕組で動いているんだ?と疑問に感じていたので、なんでもいいからスマホアプリを自分で手がけたいと悶々と思っていました。
 まぁ、それまでアプリプラットフォームチームのコードを読んだりもしていたのですが、そもそも今の組織に入ったとき、流行り始めているスマホアプリを開発したかったという思いもあって、6年越しに一通り自分で管理できる機会に恵まれました。

 作ったアプリは、HTML5canvas で生成した画像をスマホのストレージに保存する機能しか持たない簡単なアプリですが、なかなかはまりました。お遊びでアプリは作っていたのですが、ちょっと込み入ったことをすると途端にはまるのはいつまで経っても変わらないな。。
 普通のWebViewのアンカーリンク(A要素)から画像ファイルをダウンロードするのであれば、特にはまらなかったかもしれませんが、今回はbase64エンコードされた文字列をアプリで受け取って、画像ファイルにデコードして保存という流れです。というのも、WebView上の canvas に文字やスタンプ画像を好きに配置して、 toDataURL() メソッドで data:URI を生成するためです。
 この場合、 DownloadListener で受け取れるのか、 WebViewClient.shouldOverrideUrlLoading(WebView view, String url) で受け取れるのか挙動が分からずに色々悩みました。素人同然のため、API Levelで挙動が違うのか、端末依存なのか、WebViewのバージョンで違うのかが全く分からず、なんだかできたと思ったら他の端末で動かない、それを直したら他で動かないみたいなモグラたたき状態に陥っていました。
 結局、この記事に行き着き、 DownloadListener ではなく shouldOverrideUrlLoading でもないブラウザのセキュリティの更新が原因だったみたいでした。 JavascriptInterface を用意して、 data:URI をWebViewのJSから渡してもらう方法に軌道修正して期待通りの動きにできました。なんとなくアンカーリンク(A要素)だし初アプリ実装だから、王道のメソッドで受け取りたかったんだろうなと自分にかかったバイアスの強さを感じました。

pokkurutime.hatenablog.com

 だから端末によってイベントをキャッチできたりできなかったりしたんだろうなぁと振り返って思いました。他にもはまったポイントや知り得たことを忘れないように書いておこう。

  • 端末の権限の取り方
    • Android 7.0以降は onRequestPermissionsResult をOverrideしてユーザの操作をハンドリングする
    • これをOverrideしていないと権限無い状態で先の処理が走ってしまってアプリが落ちる
  • ProgressDialog の実装方法
    • Thread で非同期にしておかないと Activity で管理?していないダイアログになってアプリが落ちる
  • JSのAlertみたいなダイアログの実装方法
    • AlertDialog.Builder でボタンの挙動だったり表示文字をセットできる
  • 画面回転した場合、WebView の更新処理が走る
    • restoreState()savedInstanceState を保存しておくメソッドをOverrideしておく
  • Webアプリじゃないので、戻るボタンを押されたときの挙動を実装しておく
    • onKeyDown() をOverrideして実装しておかないと、トップページに戻った時に再度戻るボタンを押したら別のページに遷移したりする
    • この挙動がネイティブアプリっぽくないから、適切に押下を無視するとか finish() とかを実装しておく
  • アプリからWebViewのHTMLを変えたい
    • onPageFinished() とかをOverrideして loadUrl() でJSを即時実行させると動く
  • リファレンス機は必要
    • エミュレータで確認していたのですが、どうやらWebViewの更新が追いついていなかったのかな?動かなかった。。
    • Nexux5xも息が長いけど、公式リファレンス実機の確認は絶対やったほうがいい

 実装はとても楽しかったので、最後にアプリの紹介をば。 これは所属する会社のお産合宿に誘われたのがきっかけで始まったことでした。

osan.pepabo.com

 さらっと canvas で画像ファイルを・・・と話しましたが、それはアイドルのコンサートなんかで光らせるキングブレードというペンライトに装飾をするための画像ファイルです。キングブレードの内紙にバッチリ合う画像ファイルをコンビニで印刷できる(コンサートへ向かう最中でも推しメン用にカスタマイズできる)ことが売りです。
 そちら方面はまったく詳しくないのですが、 @souseiji さんや @_mmmix さんの話を聞く内にチャンスなのではないかと感じて参戦させていただいた次第です。

www.amazon.co.jp

 途中、まったくわからないキーワードに出会ったり、自分用のキングブレード買ったり、帰りにスターマリーのコンサートに連れて行ってもらったりと、開発以外にも色々初体験が多い期間で楽しかったです。

starmarie.com

 今回は、金銭的にも期間的にもAndroidアプリに絞って作りましたが、会社の人たちはiPhone率が高いので、空いた時間にちょびちょび作ってしれっとiOSアプリも公開しとこうかな〜

コードレビューのおきもち

 今日、わりとふと感じたことがあったので、一応メモしておこうかと思って書いてます。 チームでペアレビューの話が軽く出て、あぁ、そうかと納得したのですが、レビューってどう感じてますか? レビューって、以下のようなことを目的としてやってるかと思います。

  • 多角的な視点でコードを見て、不具合を減らす
  • タイポの防止
  • メンバー間のコミュニケーション
  • 人に説明できるコードを書けているかの確認

 よく言われてるから、今更どうとかではないし、上記を考えるとレビューをやる意義があると考えています。


 ただ、まだ駆け出しの頃は、レビューをするのもされるのも苦手でした。 振り返ってみて、なんで苦手だったかは、既に自分の中で答えが出ていました。

なんかケチつける or つけられてるみたいでマイナスなおきもち

 どんなにメンバーの関係が良好でも、言葉を選んでも、1%はそういう意味合いが僕にあったし感じていました。(関西人だから、自意識が高いのかなw?)
でも多分、それは今でも変わっていないので、だから、今でもする時もされる時も気を使っています。
 で、その気を遣うっていうのは、間違っていないんだとも思っています。 コードレビューじゃなくても、普段の生活をしていて、誰かに指摘や注意をする時って、気を遣いますよね。 それは、関係性をうまくし続けるために、必要な人間的活動なんだと思います。

 じゃあ、そのもやもやした意識を持ち続けて今後レビュー活動に勤しんでいても良いものかと、多分漠然と思っていたんでしょう。自分が。 でですね、やっと感じていたもやもやを打ち勝たせる魔法の考えがふと頭をよぎったのでした。


 コードレビューに対話やコミュニケーションの意義を見出すことに近いのですが、多分、僕が今後、レビューやペアレビューをするときは以下の思いを持って接していると思います。

僕の思想や考え方をあげるからちょうだい

 レビューをですね、いろんな人とすることによって、ペアになった人の思考や思想がどんな形であれ、自分の中に流れてきますよね。 それを受け取ったら、どのような感情を抱いても自分の中には入ってきてるんですよね。多分。そういう自分の考えじゃないものをいっぱい受け取って気に入ったものを反芻することで、自分の感性がブラッシュアップされていくんだと思うんですよね。
 今、僕が立っている場所はそういう活動を活発にしようとしているから、若いエンジニアは、広い視野を持ったり、知識を得る機会に恵まれて早く成長できるんだろうなと思ったんですよ。
 良いアイデアなのかそうでないのかを相対的に判断する必要はなくて、受け取った人の感性で継承していけばいいんじゃないのかな。それを個々人がコミュニティでやっていれば、自然淘汰されて良いアイデアが残っていくのではないか。自分のクローンは作らなくていいけど、いわゆる上位のエンジニアレベルの人間をできるだけ早く作るために、良いアイデアの生存競争を活発にすれば良いのではないかと。


 まぁ、であるならば生存競争の機会はレビューじゃなくても良いんだけど、レビューはわかりやすい場であるかな。 そう思えば、多少苦手でも、苦にならないなと自分は思った次第です。


 なんで今さらそんなことをふと思いついたのかはよくわかりませんが、WEB+DB PRESS を執筆させて頂く機会に恵まれた事は関係していると思います。 エンジニアになってから、よく目にする機会があった本に執筆させて頂く機会を与えてくださり、ありがとうございました。
 報告することが遅れてしまって申し訳ありませんでしたが、自分の知識の棚卸しを本にするという、なかなか体験できない経験をさせて頂いた技術評論社 池田様や、弊社メンバーに感謝しています。

gihyo.jp

Zephirはじめてみました

どうも

unionsep.hatenablog.com

前回の記事で少し話しましたが、Zephirを触ってみました
ZephirはPHPカンファレンス2015ぐらいに見聞きしていて、興味のあるプロダクトでした

zephir-lang.com

phpcon.php.gr.jp

ただ、インストールしてみるものの、なかなか実際にどんなものか確かめるまでに至らなかったのですが、おもむろに見てみたらバージョンが 0.9.8 になっててそろそろ正式版になるのではと思って焦って触りましたw


InstallationIf you’re using Ubuntu って書いてあったので、手持ちのUbuntu16.04にインストールしてみました
ホント、最近 apt でなんでもインストールできますよね
みんなUbuntu好きなのかな

インストール自体は書いてある通りに gcc やら PHP を入れて git clone して installer叩くだけなので、手順は割愛します

で、とりあえず Tutorial にあるものを作ってみました
他でも書かれてありますが、ちょっとコードを書いてPHPとの違いを意識したのは以下ぐらいでした

  • 変数の書き換えに let を指定
  • namespace は必須
  • forforeach になる
  • if で条件式に括弧いらない
  • 型がPHPより厳密
  • functionの戻り値の型をhintで指定できる

まぁ、JavaとかC#とか触ってる人だったら、あんまり意識しなくても自然な流れで書けるのではないでしょうか
聞いていた elseif が使えないというのも、このバージョンでは使えるようになっているみたい

Control Structures — Zephir 0.9.4 documentation


でですね、再帰的なarray_searchを作ろうと思ってこんなコードを書いてみたわけですが、結構ハマりました
書いたと言いましたが、多分これじゃない感じなんですけど、それっぽいものが書けたので、とりあえずコードは載せようと思いまして^^;
どうもZephirにはPHPuse に相当する機能(参照渡し)がないようで、どう解消しようかハマっていました
結果どうなのか追えていないのですが、以下あたりで議論されてるみたいです

github.com

namespace Utils;

class Arrays
{
    private static keys;

    public static function arraySearchRecursive(var needle, var heystack) -> array
    {
        let self::keys = [];
        self::search(needle, heystack);
        return self::keys;
    }

    private static function search(var needle, var heystack) -> bool
    {
        var key, val;
        for key, val in heystack {
            if is_array(val) {
                self::search(needle, val);
            } else {
                if needle == val {
                    let self::keys[] = key;
                }                
            }
        }
        return;
    }

}

どうやらSuperGlobal、_POSTの値とかも取れるみたいで、取る必要ないんちゃうかなとか思いました^^;
言語仕様は、そこまで込み入った感じはなかったのですが、function-> 指定で返却値の型をhintで書けるぐらいが独特かなぁというぐらいでした

【PHPカンファレンス2016 フォローアップ】Cygamesを支えるPHPについて講演しました | Cygames Engineers' Blog

こちらの記事にも書かれている通り、配列処理で高速化が見込めるそうです
僕も計測をしてみたかったのですが、わりとハマったので一旦ここで断念しましたが、ソーシャルゲームを開発していた時は、巨大な配列処理を行う事が結構あったので、ガチャロジックとか開催中のイベント配列とかで使えないかなと思いました
あと、単純にextension化することで、簡単に処理を変更できなくするという意味でも有効性を見いだせる気がしています

今はゲーム事業から離れてしまったので、Zephirを使って高速化を試みるより、リファクタリングやDBの正規化で事足りますが、いつかキラーコンテンツとして提案できる日が来たらいいなぁと思っています
いつか使うど!

UbuntuデフォのFirefoxはてな開くと英語になったのにちょっと感動した!)

再帰的なarray_searchを作った

どもども

すっかり春な気候になってきて、フレッシュな方々を目にする機会が多くなりました。 フレッシュな感じに羨望の眼差しを向けながら、ここ4年ぐらい、ず〜っとPHPをメインに扱っています。

う〜ん、もともとず〜っとJavaPerlな感じの人で、敷居が低いと噂のPHPを横目に見てましたが、ソシャゲを扱うようになってPHPも扱うようになったんですが、もはや何を作るにしても、もうPHPでよくね?みたいな脳みそになってしまったのが良かったのか悪かったのか。。
まぁ、アンチPHPな方もお見かけしますし、当然、僕自身もPHPのこの言語仕様はやだな〜って思ったりしたりすることもあります。 確かにレガシーなシステムを扱うときに、脳みそひねくり回さないとダメなときもあるし、PHPマニュアル 見てて、え、そうなの。。って思うときもあります。
でも地雷を踏まない能力ってエンジニアに必要な能力の一つだったりするのかなぁって今は思っています。 そういう能力を養うコストと、PHP の強力な文字列処理や配列処理の恩恵を受けるメリットを天秤にかけて、やっぱPHPで良いかなぁと思う次第です。。


個人的なコラムはどうでもいいとして、僕がPHPを気に入っている面の一つが配列処理です。 array_column とか usort とかは超気にいってますが、 array_search が単一階層の配列しか検索してくれない。。 配列の中に設定項目を連想配列にして詰め込んで、特定の設定項目を探し出すっていうケース、ソシャゲでは結構あったんですが、ソシャゲから離れた今、またそういう要件に対峙する時が来ました。 あれ、こういうパターンって確か共通関数にしてたなぁって思ったので、はてブから「そろそろブログ書かない?」メールも来たので、筆を取る気になりました。

想定している連想配列は以下のような構造です。

$event = array(
    array(
        'start_date' => '2017-01-01 00:00:00',
        'end_date' => '2017-03-31 23:59:59',
        'key' => 'event_1'
    ),
    array(
        'start_date' => '2017-02-01 00:00:00',
        'end_date' => '2017-04-31 23:59:59',
        'key' => 'event_2'
    ),
);

keyevent_2 の要素番号を取りたいといった場面がよくありました。 ここで、 array_search でって思うのですが、このような配列の中に配列がある多重配列構造の場合、思った通りの動作をしてくれません。 そこで、こんな関数を作ってみました。

function array_search_recursive($needle, $heystack)
{
    $keys = array();
    $func = function ($needle, $heystack) use (&$func, &$keys) {
        foreach ($heystack as $key => $val) {
            if (is_array($val)) {
                if ($func($needle, $val) !== false) {
                    $keys[] = $key;
                }
            }
            if ($needle == $val) {
                $keys[] = $key;
            }
        }
        return false;
    };
    $func($needle, $heystack);
    return $keys;
}

$needle に検索したいキーワード、 $heystack に検索対象の配列を指定すると、検索に引っかかる要素番号を配列で返します。 検索に引っかからなかった場合は、空配列を返します。

これ、同じことを考えておられた方がいて、こちらのQiitaの記事を参考にしました。

qiita.com

記事では検索キーワードに引っかかった最初の要素番号を返却していますが、仰っている通り、引っかかった全ての要素番号を返却した方が、汎用性が高くなりそうだったので、少し改良しています。 とは言え、クロージャに配列を参照渡しして、引っかかったら配列に詰めるってとこしか改良していませんが。。^^;


あと、 start_date2017-01-15 00:00:00 以降の要素番号とかを取りたいとかもありますよね。
そういう場合は、 array_search_recursive に関数を渡せるようにしてあげたら、もっとかっこよくなるかなぁ。 でも今回はそういう要件ではなかったので、脳みそ終了しました。


クロージャを代入した変数を参照渡しして、検索対象が配列だった場合、どんどん下層まで検索していくので、リークしないか気になるとこですが、そこそこ使えるのではないでしょうか
とは言え、 array_column で似たようなことできるよね〜とも思うのですが、実は開発環境は5.3みたいなとこあるのでは?

こういうの、Zephirで実装できたらかっこいいよねぇ

zephir-lang.com

最後に、いつぞやのPHPカンファレンスで見た尊敬するRasmus Lerdorfのインタビュー記事でも載せておこうかな〜

gihyo.jp

続 select結果をcsvでファイル出力

unionsep.hatenablog.com

先月、mysqlinto outfile でレコードをCSVファイルにサクッと取ってた記事を書きました
で、これは知ってるんだけど、今回はこれじゃダメなんだよな〜ってときに、どうしていたか思い出したので、記事書く気になりました

というのも、1. into outfile を使うと access denied になる場合も権限によってはあるね
2. mysqlsshトンネルの先にあって、その先のsshは許可されてないの・・・とか
3. ファイルにしたら巨大過ぎて、そんなの置くスペースないぜ!とか

過去を思い返すと、3. のパターンはあったな〜という思い出です
確かあれはサーバフルリプレイスしてたときに、あのvmが・・・

まぁ、というわけなんですが、手順はこんな感じ
1. mysql コマンドに -e つけてSQLを投げる
2. 1. はタブ区切りで出力されるから、 sed でタブをカンマに変換
3. csvファイルにリダイレクトする

1 . のSQLですが、文字列系はダブルクオーテーションで囲う

select
  concat('"', hoge, '"') as hoge,
  hige as hige,
  concat('"', huga, '"') as huga
from hogehoge

ワンライナーで書けるSQLだったら -e に続けて書いてもいいけど、 concat とか使ってたらそうも行かないので、SQLファイルにしておく
で、これでファイルを cat してクエリをmysqlに流し込んで、ファイルにリダイレクトしておく
この時、mysqlはタブ区切りに出力してくれるみたいなので、なんとなく拡張子を tsv にしてます

mysql -uroot hohoidb -e "`cat hogehoge.sql`" > ./hoge.tsv

2 . 3 . sed でタブをカンマに変換してリダイレクト

sed -e 's/\t/,/g' ./hoge.tsv > hoge.csv

てな感じでヌルっと出してます
まぁ、だいたいSQL用意してこんな感じでバーンとしてます

mysql -uroot hohoidb -e "`cat hogehoge.sql`" | sed -e 's/\t/,/g' > hogehoge.csv

あ、前回書き忘れたけど、csvmysqlにインポートするときはこんなSQL使ってます

LOAD DATA LOCAL INFILE '/home/ore/hogehoge.csv' INTO TABLE hogehoge FIELDS TERMINATED BY ',' ENCLOSED BY '"'

自分が完全に管理してるDBだったら、ALTER やっちゃえ!とかも思うんですが、「別チームんとこだしなー」「申請とかなー」とか思う時、エンジニアだったらあるはず!
そんなときに、サックリ自己解決したいときに使ってます
遅せークエリがボトルネックになってるんだけど、ローカルにデータ欲しいわーって思ったときとかかなぁ

今のところ、この2つの手法でデータが取れなかったことはないかなぁ


こういうのって、結構レガシーな手法で、知ってても自慢できるようなことじゃないんだけど、知らない人は知らない感じなのかなぁ
僕は結構頻繁に使うのですが、知らない人は、思いつかない限り使わないのかなぁと思ったりしてます
これらの気にいってるとこは、 select しか投げてないで!ってとこ
流石に select で壊れないっしょっていう安心感があるw
ブログ書いてるの、わりと会社の人にバレた気配がするので、役立ってくれたらうれしいなぁ〜

jQuery.DataTablesで完全一致検索

はまった〜

知らなかったけど、とっても便利なjQueryプラグインがあって、むしろ便利過ぎて使い所迷っちゃうなって感じ
ページング実装するだけで使ってもいいじゃない
2年前ぐらいの記事が多かったから、わりと前から流行ってたみたいなんで、使い方については先人様に任せます

DataTables | Table plug-in for jQuery

で、これ、インクリメンタルサーチっぽい動きがデフォルトで、とってもかっちょいいんですが、使い所によっては完全一致させたいとかあるのかも
キーワードを入れてサブミットさせて、テーブルに並べるデータをHTML生成時に絞ってやるって方法が真っ先に頭に浮かんだんす
実装で迷ったのは、JSでスイスイ検索できるのに、完全一致させるためだけにサーバサイドに頼るってのもかっちょ悪いなと
なんか知らないけど、 DataTable() で指定するパラメタにちょちょいって書いてやれば、すぐできんじゃない?って思ってました

そんなの速攻できるっしょってたかをくくってたんですが、わりとそうでもなくて1日かかっちゃいました
まぁ、ドキュメントは英語だし、このプラグインで完全一致させたいなんて思うやつはあんまりいなかったのかなぁ
で、 jquery.dataTables.js の中身を読む旅に出発

$('#example').DataTable(
  "oSearch": {"bSmart": false}
)

Namespace: oSearch - documentation

まずこれでスマートなフィルタリングを使わないに設定

$('#example')
  .DataTable()
  .search("^" + $('#text_area').val() + "$", true, false, true)
  .draw();

つぎにこれで正規表現を使って1語検索をするのが正解みたい
結局stack overflowを参考にさせていただきました

stackoverflow.com

ここにも書いてましたけど、stack overflowのが僕にとってやさしかったです

Google Code Archive - Long-term storage for Google Code Project Hosting.

いいじゃんいいじゃんって嬉しがってたら、また一難
テキストエリアにキーワードを入れて、次に消していって、最後まで消したら、最後の検索が残ってる(´・ω・`)

if ('' != $('#text_area').val()) {
  $('#example')
    .DataTable()
    .search("^" + $('#text_area').val() + "$", true, false, true)
    .draw();
} else {
  $('#example')
    .DataTable()
    .search('')
    .draw();
}

テキスト内にキーワードが入ってないときは空で draw() したら、とりあえずは期待通りの動きをしてくれた〜
clear() とか destroy() 使ったら、デフォルトで設定したパラメタも初期化されてしまってどうしていいかわからなかったw
oSearch とか aoColumnDefs とかで設定できるのかなと思ったんだけど、ここまでにたどり着くまで半日かかってるので燃料が切れて一旦終了。。

なんでこの記事を書こうと思ったかというと、DataTablesは調べれば調べるほど高機能なプラグインだなーって思ったんですけど、便利過ぎて完全一致なんてパラメタですぐっしょって思ってしまったから
だから、多分、次同じことをしようとした時、まーた性懲りもなくすぐっしょて思いそうだったから。。
これだけスイスイ検索できることがウリ?なのに、完全一致検索は違うんじゃない?って今になって思います(´・ω・`)

ブログを書く訓練として、思いついたことを書くのを最近心がけてますが、結構ネタはあるみたい
ただ、やっぱまだ書くのに時間がかかってて、もっと精進せねばと思う感じです〜

select結果をcsvでファイル出力

自分では結構前から便利に使ってるんだけど、意外に重宝されることが最近ありました

それがこれ

select * from hoge
into outfile '/tmp/hoge.csv'
fields terminated by ','
optionally enclosed by '"'

私の使いみちとしては、

  • 障害対応なんかで、多分このレコードがおかしいんだけど、100%自信がないときに試したい
  • 二日酔いのときに、絶対間違えられないクエリを流すとき
  • ローカルじゃない別の環境で起きてる不具合のデータをサクッととってきたいとき
  • 単にダンプが面倒なとき
  • シャーディングしてるDBで、他のDBへの参照権限がない場合、grantするの大げさやなって思ったときにファイルに出してシェルとかawkでコネコネするとき

/tmp/var/tmp 配下にcsvファイルを落として、ファイル自体の消し忘れを防止することを意識的にしてたりする

ま、mysqlの実行ユーザーにパーミッションがないところには落とせないんですけど

時と場合によっては fields terminated by の指定を変えて、タブ区切りとかスペース区切りにしてシェルとかawkperlで使いやすい形にしたりする

前のプロジェクトの方針?として、「直せるんだったらなにしてもいい」っていうなんともファンキーな空気で、まぁ立ち上げ近くからいたからかもしれませんが、、クエリ打つときは begin commit rollback してって先輩に口酸っぱく言われてました

ただ、家でたら鍵かけたかな?って思うぐらい、一時記憶装置の容量が少ない私は不安になってしまってこういう若干過度なテクニックがわりと発達したのかも

まぁ、もう大体忘れちゃいましたけど・・・

最近はMySQLばっかのプロジェクトに関わることが多いのですが、OracleとかPostgresqlとかにも同様の機能があるのかなぁ