Flutterで英語をやってくためのDuo3.0アプリを作ってみた話
前回、FlutterとDartをしばらくやってみると言ってたこともあって、土日にアリアンデルに籠りながら、Flutterをやっていた。アリアンデルというのはダークソウル3のDLCのことですので、アリアンデルの絵画世界に興味がある人は一緒にやりましょう。
TourとかDart bootstrapとかを眺めたり、実際に動かしながら見ていたわけだけど、途中から眠くなって、アプリでも作りながら困ったらドキュメントに戻ればいいかーといつもの雑さでアプリを作り始めた。 最近、Duo3.0をやってるので、オフラインでもディクテーションとか暗記(覚えれば音読をいつでもできる)とかできるといいよねって思って、Duo3.0アプリにした。
成果
以下、成果になります。
アリアンデルに篭りながらFlutterをやった結果です。ご査収ください。 pic.twitter.com/D9uf96krc2
— レベルがあまりに低い (@terut) November 15, 2016
ソースコードも置いとく。
TL;DR
FlutterのUIはシンプルなものなら割と作りやすいと思う。HTMLっぽさがあってとっつきやすかった。ただその分、オブジェクトを生成しまくってて大丈夫なんかなとも思った。完全にFlutterという言語を書いてる気分で、AndroidやiOSのこと知らなくてもそれなりのものは書けそう。
StatelessとStatefulなWidgetがあって、Reactiveな仕組みが導入されてるのはやっぱり良い。Stateを変えるとWidgetを生成し直しているようで、差分更新って書いてあった気がする。Reactにインスパイア受けてるってことで、ReactNativeに似ているような気がした。
困ってもExamplesが結構充実してるのでなんとかなることもあるんだけど、ドキュメント、Examples、テストコードで見当たらないとググったところでほとんど情報が出てこないのが最高。ただ最終的には実装の詳細が見れるので、まあ良いかなとも思う。
実装で詰まった部分
基本的にはStatelessかStatefulなWidgetどちらかを選んで実装していく。setStateのコールバック内でデータを設定すると、再レンダリングが走る感じ。ReactNativeとかもそんな感じだったような? よく分からないことだらけで、細かくあげるとキリがないので、幾つか載せておく。
画面遷移
まず画面遷移からしてよく分からんかった。最初にRoutingの設定をするんだけど、実際の呼び出しでは引数を渡せなさそうな感じで、これオブジェクト渡したい場合どうするんだろうってなった。多分現時点では渡せなくて、GithubのIssueに次のマイルストーンのタグがついてたからそういうことなんだろう。 人によってはRoutingを"/section/$section_id"とかして、Hookを使って自分でパースしたりしてたけど、マジで言ってる?ってなって他の方法を探した結果、名前で遷移するだけじゃなく、Routeオブジェクトを生成して遷移できそうだったので、そっちでWidgetオブジェクトを初期化して遷移することにした。 マジで言ってる?とか思った割に僕もWayfaringとかで同じことしてるんで、何も言えないことに後で気づいた。
実際のコードは以下のような感じ。
// 1. 設定するパターン。これだと引数が渡せない。 void main() { final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ '/': (BuildContext context) => new SectionList(), '/sentences': (BuildContext context) => new SentenceList(), }; runApp(new MaterialApp( title: 'Duo 3.0', initialRoute: "/", routes: routes )); } // 適当なハンドラで遷移 。pushNamedはFutureオブジェクトを返すんだけど、評価後のオブジェクトにWidgetオブジェクトが返ってくるわけじゃないので、詰み。 void _handleSectionSelected(Section section) { Navigator.pushNamed(context, "/sentences"); }
// 2. Routeオブジェクトを使うパターン。引数が渡せる。 void main() { runApp(new MaterialApp( title: 'Duo 3.0', home: new SectionList(), )); } // 適当なハンドラで遷移 void _handleSectionSelected(Section section) { final route = new MaterialPageRoute<Null>( builder: (BuildContext context) => new SentenceList(section: section), ); Navigator.push(context, route); }
InputのonSubmittedが発火不可
InputオブジェクトにはonChangedとonSubmittedってコールバックがあって、打ち込まれたセンテンスの判定に使うかーと思ってたら、キーボードのReturnをDoneに変える方法が分からなくて詰んだ。onSubmittedはDoneを押した時に発火するっぽい。エミュレートできそうなperformActionってメソッドがあったけど、なんかうまくいかなかった。 あとInputはPlaceholderを入れられるんだけど、Inputだけで使うとPlaceholderの文字が残り続ける。なんで、普通にFormオブジェクトとInputFormFieldを使うのが良さそうだった。InputFormFieldはvalidatorを設定できるのだけど、初期表示時と入力するたびにチェックが走ってウケる。この辺はExamplesかテストコードを眺めると使い方が分かる。
日本語が表示不可
日本語表示しようとしたら表示できなかった。これはフォントを変えりゃイケそうってことで、TextStyleで色とかフォントファミリーとか選べるので、それで設定すれば良い。何となくHTMLぽさが少しある気もする。
final text = new Center(new Text( "sample", style: new TextStyle(fontFamily: "Hiragino Kaku Gothic ProN"), ), );
まとめ
ドキュメントもExamplesも充実してるし、テストコードを見ればハマりながらもなんとか実装できる。どれにもない場合は、コード読んで自分で考えるしかない。ググってもマジでほとんど何も出ない。 Rectiveな仕組みをデフォルトでサポートしてるのは非常に良い。Androidは確か既にサポートしてた気がするので、iOSも是非お願いしたい。Reactっぽさがすごいあるので、その辺に慣れてる人は入りやすいと思う。 完全にFlutterという言語を書いてる気分で、AndroidやiOSのこと知らなくてもそれなりのものは書けそう。 使ってるうちに音出したいなーと思ったんだけど、"Remove sound.dart"というコミットを発見してウッてなってるのが今。
Flutterに興味が出た人はこちらも参考にするといいかもしれない。
話は変わるが、この間"やっていき宣言"なるものが行われ、いいなーと思ったので、おっさんだけどやっていくことにした。
異常に怠惰なので気を抜くと気持ちだけ、一発にもならない何かみたいなので終了してしまうのが正直なところではあるが、気負わずやっていき。
Flutterの開発環境を作った話
なんとなく思い立ってFlutterでも触ってみようかなと思って、開発環境を作ってみた。 FlutterというのはGoogleが作ってるiOSとAndroidを一つのソースで開発できるというフレームワークで、Dartを使っているらしい。ハイパフォーマンス、iOSとAndroidのUI/UXの再現度が高いことが売りとのこと。 high-fidelityと書いてあったので、そういうニュアンスで捉えてるけど、間違ってたら教えて欲しい。
とりあえず上記のサイトに従ってやっていけば良さそうだったのだけど、ちょっと詰まったところとか試したことが幾つかあったので、そこだけメモとして残しておく。
TL;DR
素直にAndroid StudioとIntelliJ入れるのが楽で、Atomはプラグイン新しくならないのでやめたほうが良い。あとはJava 1.8を使えば、特に問題なくiOSとAndroid共に起動できる。
IDEのインストール
Setup作業を進めていくと、Android StudioとIntelliJの2つのIDEをインストールする流れになる。 Android StudioはAndroid SDKとAndroid SDK Platform-Toolsを入れるためにインストールし、コードを書くのに最高の体験を得たければ、Pluginを用意したんでIntelliJを使いましょうということっぽい。 コマンドラインツール使うなら、エディタは何でもいける。
IDE入れまくるのやだなー、Android StudioってIntelliJがベースじゃなかったっけ?と思って、Android Studioでプラグインを検索してみると、DartはあるけどFlutterは見つからなかった。
ググるとAtomで開発してたりして、確かにDartとFlutterのプラグインはあるけど、そもそもflutter doctor
の結果が違うことに気づいて調べてみると、Atomのプラグインはメジャーバージョンアップしないってことだったので、プラグイン使うなら大人しくIntelliJが良さそう。おそらくちょっと前まではAtomが推奨されていて、コマンドラインの結果がAtomで表示されてたってことなんだと思う。
[-] IntelliJ IDEA Community Edition (version 2016.2.5) • Dart plugin not installed; this adds Dart specific functionality. • Flutter plugin not installed; this adds Flutter specific functionality. • For information about managing plugins, see https://www.jetbrains.com/help/idea/2016.2/managing-plugins.html
https://groups.google.com/forum/#!topic/flutter-dev/JEzfu0kpmKA
まあよくよく考えるとAndroid StudioでiOSアプリを起動するのもウケる気がするので、複数のIDE入れなってことなのかな。
Javaのバージョンについて
Setupして実際にデバイスを指定して起動しようとすると、エラーが出た。
Java 1.8が必要そうだったので、ダウンロードして入れたらiOSとAndroid両方で動いた。
まとめ
一つのソースで開発できるの最高!みたいな話なのかもしれないけど、現実問題としてiOSとAndroidでの最高の体験を求めた場合、設計思想の違いによりUIは違うものになると思うので、一旦落ち着いて考えていきましょうという気持ちはありつつも、ビューだけ分けるとかなんかそういうのもあるかもしれないし、見てみるのは良いと思う。 Dart触るの初めてなので、しばらく楽しんでみることにする。
Arch LinuxでActivitiesを使うとカーソルが消える話
TL;DR
Gnome3を使ってて、Activitiesをクリックしたら波アニメーションの後にマウスカーソルが消えてしまうという人へのワークアラウンドは以下。
$ yaourt -U /var/cache/pacman/pkg/mutter-3.18.2-1-x86_64.pkg.tar.xz
背景
いつの頃からか忘れたけど、Activities Hot Cornerを使うときにマウスカーソルが消えてしまって、異常に不便になった。 Activities Hot Corner(アクティビティ)という呼び方が正しいのかもわからないけど、ArchWikiにはそう書いてあったからそう呼ぶことにする。
以下のサイトの最上部にある画像のようなメニューとワークスペースとウィンドウが俯瞰できる状態のときに消えてしまうので、Tabとかで選択してた。
解決法
色々調べた感じだとmutterが悪いんじゃないかという当たりをつけたので、キャッシュからダウングレードを実行する。 ちなみにmutterは何かというとGNOME用のウィンドウマネージャとのこと。
$ yaourt -U /var/cache/pacman/pkg/mutter-3.18.2-1-x86_64.pkg.tar.xz
ただこれはワークアラウンドでしかないので、ちゃんとアップデートを確認しつつ、アップデートが出たら上げていくのがよいと思う。
Bugsにちゃんと報告上がってた。
Alamofireで204 No ContentなAPIのレスポンスをどう扱うか
TL;DR
現状、SwiftのHTTPクライアントであるAlamofireで204 No ContentなAPIの処理のベストプラクティスは存在しないので、response
で受けるか、Response Serializerを作るか、自分でResponseを作って泥臭くやるしかない。
Response Serializerを作るのが、他のAPIの処理と同じように実装できていいように僕は思う。
背景
Swift 2.0とiOS 9対応をしていて、Alamofireのバージョンが上がったことで、AlamofireがResponse(Struct)とResult(Enum)を持つようになった。
それに伴い、responseJSON
やresponseString
などはデータが返ってくることを期待した実装に変更され、通信完了時のハンドラにResultを含むResponseが渡されるようになった。
(以前、使っていたバージョンではそういったものはなく、Request, Response, Data, ErrorがOptionalで渡される実装だったので、自前でResponseを定義して扱っていた。)
// 以前: ハンドラにrequest, response, json, errorのOptionalが渡されている Alamofire.request(urlRequest) .validate(statusCode: 200..<300) .responseJSON { (request, response, json, error) in // code } // 現在: ハンドラにResponse<AnyObject, NSError>が渡される Alamofire.request(urlRequest) .validate(statusCode: 200..<300) .responseJSON { response in // code }
response
以外の実装ではデータが返ってくることを期待しているので、204 No Contentの場合にシリアライズエラーとなって処理が失敗したことになってしまう。
対策
対策は幾つか思いつく。
response
はresponseJSON
やresponseString
から呼び出されるメソッドで、通信完了時に呼び出されるハンドラにRequest, Response, Data, ErrorがOptionalで返ってくるので、他のAPIとは別の方法でデータを処理するresponse
とAlamofireのResponse、Resultを組み合わせて、他のAPIと同じ処理ができるようにする
けど上記の方法はいずれもアドホックな対応になり、微妙な気がしてやめた。
Issueを巡っていると、コミッタの人が例えばNo Dataを扱うResponse Serializerなんかあったらどうかみたいな話をしていて、Response Serializer作れることが分かった。
Response Serializerを作るとresponseJSON
と同じようにレスポンスを扱えるので便利そうだし、作るぞ!!という気持ちになったので作った。
実装
ドキュメントにXMLのResponse Serializerのサンプルがあったので、これを参考にした。
https://github.com/Alamofire/Alamofire#creating-a-custom-response-serializer
extension Request { public static func NoContentResponseSerializer() -> ResponseSerializer<AnyObject, NSError> { return ResponseSerializer { request, response, data, error in guard error == nil else { return .Failure(error!) } guard let _ = data else { let failureReason = "Data should not exist. Input data should nil." let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason) return .Failure(error) } return .Success("") } } public func responseNoContent(completionHandler: Response<AnyObject, NSError> -> Void) -> Self { return response(responseSerializer: Request.NoContentResponseSerializer(), completionHandler: completionHandler) } }
.Success("")
で空文字を返してるのはちょっとした理由があって、responseJSON
がResponse<AnyObject, NSError>
を返してくるので、そこを揃えたかったため。
本来であればnil
を入れて返したいところで、もうちょっとうまい実装があればOptionalとして返したりできそうだが、OptionalとAnyObjectを同じように扱うにはAnyを使う必要があって、現時点ではSwfit 2.0とiOS 9対応を優先させて、とりあえずこれで動くようにしてる。
あとは呼び出し側で、
Alamofire.request(urlRequest) .validate(statusCode: [204]) .responseNoContent { response in // code }
のように呼び出せば良い。
まとめ
他にいい方法があったり、こんなやり方イケてないだろって話があれば、ぜひ教えていただきたく、何卒。 AnyObjectは割とよく使うけど、Anyって結構使うもんなんだろうか…。
作れないかもしれないけど作った話 at MF Geeks Night
TL;DR
最近Swiftに入門してて、作れないかもしれないけど勢いでライブラリを作った。 練習も兼ねて自分に必要なライブラリを作ると他のライブラリの作りを調べたりとかSwiftっぽい書き方とか目にできるので、学びがあってよい。 国家権力が白紙に戻すこともないので、作れないかもしれなくても勢いで作るぞ!!
LT at MF Geeks Night
先月に引き続き、@ppworks さんに声をかけてもらってMF Geeks Nightに行ってきた。 MF Geeks Nightについては MF Geeks Nightに行ってきた - 解せぬ日記 を見てもらうとよい。
LT募集です!と言われながらも、珍しく仕事が忙しいのとチキンなのがあって資料ができるか分からなかったのでLTに関しては返事をしてなかったんだけど、開始1時間前ぐらいにウオオオオオオオオオオオオオとなって資料を作り始めたら、雑すぎる資料ができたんで、枠空いてたらやることに決めた。
ピーさんが枠くれた時にはすでに話してたよね
— レベルがあまりに低い (@terut) 2015, 7月 29
枠空いてたらやることに決めたはずなんだけど、気がついたらLTしてた。
まとめ
- 新しい言語をやるときにライブラリを作ると色々学びがあってよい。
- 作れないかもしれないけど作るぞ!!ぐらいの勢いが大事。
競技場に触れたかっただけかな?
MF Geeks Nightに行ってきた
TL;DR
プロダクトを作る上での文化が醸成されつつある雰囲気を感じつつも、自分で色々やっていけそうな余地もあり、楽しそうな職場だったんでオススメだと思う。興味ある人はぜひマネーフォワードに遊びに行ってみるとよさそう。
事の始まり
@ppworksさんがエスパーになった結果、MF Geeks Nightというのがあるからどう?みたいな話をもらった。以前から開催されていたようで、ブログにも様子が上がってる。
毎回ゲストを呼んで開催しているとのことで、他の会社の勉強会に参加するのは結構新鮮だった。
やったこと
毎回、面白くなるように色々考えられているようで、今回は社内の各チームの開発環境や開発フロー、取り組んでいることについて発表するスタイルだった。すごく色んな取り組みを自発的にやっているようで、各チームがよりよい開発を目指してたのが印象的だった。
一応外部の人間に言ってた内容なので、公開してもよい情報だろうけど、ハッシュタグなどはなかったんで、内容については触れない。
まとめ
とにかくマネーフォワード各位に勢いがあって、最終的に入社だぁ〜みたいな奇声を発するようになっていた。奇行種かな?
何が起こったのか分からなかったけど、とにかく勢いがある(大事なことなので2回ry)上に技術の話もできて最高〜。
僕はぼっち力がだいぶ高いので、ぼっちになるかな〜と思ってたんだけどppさんに色々配慮していただいて、エンジニアの方を紹介してもらったりして楽しく過ごせた。感謝しかない!
systemd-tty-ask-password-agentの使い方
TL;DR
Please enter password with the systemd-tty-ask-password-agent tool!
と言われたら、別のパネルなりウィンドウなり開いて、systemd-tty-ask-password-agent --query
を実行すれば良い。
systemd-tty-ask-password-agentの使い方
Arch LinuxはSystemdを採用してて、Systemdを使ってOpenVPNとかでVPN接続するときに以下のようなメッセージが表示されることがある。
$ systemctl start openvpn@client Password entry required for 'Enter Private Key Password:' (PID 6145). Please enter password with the systemd-tty-ask-password-agent tool!
メッセージにはsystemd-tty-ask-password-agent
を使ってパスワードを入れてねと書いてあるわけだけど、ノリでそのままパスワード入力するとパスワードがまんま表示され、タイムアウトの後に次のインプットとして入力され、実行される。
どうやってパスワードを入力すんだぜ?と思ってたら見つけた。
issue with Vpn HideMyAss / Newbie Corner / Arch Linux Forums
別のターミナル開いて実行するといいよとのこと。
$ sudo systemd-tty-ask-password-agent --query [sudo] password for terut: Enter Private Key Password: ****************
入力するとこんな感じでパスフレーズが聞かれて、接続が確立した。
ちなみにもっとよいやり方がないかとドキュメントを見てみたけど、systemd-tty-ask-password-agent
はこういった使い方をするものっぽい。
Ubuntu Manpage: systemd-tty-ask-password-agent - List or process pending systemd
プロセスを見ていると/usr/bin/systemd-tty-ask-password-agent --wall
というのがあったんで、こいつがフォワードしてメッセージを出してるってことなのかな。
あとOpenVPNには--askpass [file]
ってコマンドライン引数があってファイルにパスワードを書いてもよさそうだったけど、嫌だったので却下した。
ちなみにOpenVPNのSystemdのUnitはリスト表示するとopenvpn@.service
ってなってるんだけど、これはUnitファイルの中で引数を受け取るためで、起動はリストで表示されたものではなく、@のあとに引数を与えて起動することになる。
# systemctl start openvpn@[設定ファイル名]のような感じで、もしclient.confを読むなら $ systemctl start openvpn@client