英語をやっていくためのDuo3.0デスクトップアプリを作った話
以前、英語をやっていくためのDuo3.0アプリを作り直した話 - 解せぬ日記で書いたとおりReactNativeで作ったモバイルアプリを使ってDuo3.0をやってきたわけだけど、それをやっていると今度はデスクトップアプリが欲しくなってきた。やっぱりタイピングはそっちのほうが早く時間も節約できる。僕は普段、ArchLinuxを使ってて、仕事ではMacOSXを使っているので、気がついたらElectronを使って作ってた。Duo3.0アプリ芸人みたいな様相を呈してきたけど、僕はボキャビルがしたいだけなんだ。
成果
以下、成果になります。
ソースコードも置いとく。小さいのでElectronやってみたい人が雰囲気を眺めるには良いかも知れない。
TL;DR
Electronに関してはサンプル程度だが前に書いたこともあり、中身はJavascriptとHTMLで書くということで、React入れて、ロジックとJSX書いて、トランスパイルして、JS読み込むという感じなのでシュッとアプリを書くには非常によい。
実装に関して
難易度
今回はデスクトップアプリとはいえ、メニューバーに常駐したりするわけでもなく、アプリ自体のメニューとかも別にどうでも良かったので、Electronで実装されてるAPIにはあまり触れてない。MenuItemとかTrayとかそういう類のやつ。たぶんこの辺を触り始めると、これぞElectronという特有の知識が必要になって、難易度も上がるんだろうと思う。そこを触らないのであれば、APIを眺めることもあまりないと思う。
アプリサイズ
これはよく言われてることで、そのままビルドすると異常なサイズになる。普通にやってしまうとnode_modulesとかも一緒にいれてしまうのが原因で、いちいちnode_modulesやら必要ないものは外す必要がある。最初、何も考えずにビルドしたら400MBぐらいあってウケた。色々含めないようにして100MBぐらいになった。音声ファイルが40-50MBぐらいあるので、まあそんなもんかなという感じ。
音声の再生
これはもうHTML5使えるからめちゃめちゃ楽でaudioタグを使って終わり、何も難しいことはない。FlutterやReactNativeでは大変だったので有り難し。
Diff
モバイルアプリを使ってて見にくいなーと思ったのが、一瞬でどこ間違ったか分からないことだった。間違った箇所を確認するのに時間を取られてしまい、効率的ではない。ということで、diffを導入して、差分で間違ったところだけを赤くするようにした。diffライブラリを使っていて、addedのステータスの単語を赤く表示している。ただこれ回答側に余分な単語、例えば冠詞がないのに冠詞をつけていた場合は、何も表示されないのでなんとかしたいが、いいUIが思いつかない。とりあえずaddedだけでも必要十分な気がしたので、これでやっていってる。
まとめ
全体としてシュッと出来たなという感じ。フロントエンドマンならもっと早くできるんだろう。2ヶ月前ぐらいに作って、これ使ってやりまくってたら、45セクションを2日で終わらすみたいなこともできるようになってきて、めでたい。何気にセンテンスのjsonを別の例文に変えても使えるので、新しく覚えたい例文とか足していってもいいかもしれないですね。
やっていき。
英語をやっていくためのDuo3.0アプリを作り直した話
先日、Flutterで英語をやってくためのDuo3.0アプリを作ってみた話 - 解せぬ日記というのを書いたわけだけど、そのアプリに音読用の機能が欲しいなということで追加を検討した結果、Flutterで作ったものをReact Nativeで作り直した。
成果
以下、成果になります。
ソースコードも置いとく。スタイルやらちょっと整理したい。
TL;DR
React NativeはJavascriptとReactという慣れた言語で書けることや割とライブラリとかもあって良い反面、iOSとAndroidでAPIを分けたりしてる部分があったりして、そこがすごく気になった。コードを無理に共通化しようとすると分岐が出てきて、なんとなくそのうちやっぱSwiftやJavaで作り直したほうがいいねとなるかもしれない。Dartという言語で新しいFrameworkを学ぶ必要があり、対応してるAPIはまだ少ないものの僕個人としてはFlutterが実現しようとしてることのほうが好きだし、コードも楽しく書けた気がする。シンプルなものはFlutterでiOSとAndroidを動かし、それなりの表現がしたいものはSwiftやJavaで個別に作るみたいな流れが好ましい。FlutterはGoogleが作ってる新しいOSのFuchsiaのUIレイヤーに採用されるみたいで、コミットも活発だし期待したい。
作り直した背景
音読機能を追加するにあたり、音声を流すための調査をした。その結果、Flutterには現在のところ音声ファイルを扱えるAPIが存在しないということが分かった。正確にはメンテする人がいないということでオープンなリポジトリからはAPIを削除したとのこと。Privateリポジトリに移したから使いたい人やメンテしたい人はgoogleグループに連絡くれとのことだったのだけど、前に公開されてたコード読んだ感じ、結構難しかったのでReact Nativeで作り直すことにした。
React Nativeを選んだ背景には、音声ファイルを扱えるライブラリがあったこと、AndroidとiOSのアプリをビルドできたこと、そしてReactとJavascriptをもう少し触っとこうと思ったことがある。個別に作りたいアプリじゃないし、iPhoneを使ってる妻も使えるようにということで、FlutterだったりReact Nativieを選んでる。
実装で詰まった部分
ReactとJavascriptで書けるので、まあなんとか書けたものの設定やらなんやらで割とハマった。thisを束縛したりとかはReactでお馴染みだったので、React書ける人ならビルド周辺以外はハマることは少ないような気はする。ライブラリも結構色々あって探せばなんかありそう。
Buildの設定
React NativeのCLIから作られるアプリの中にはAndroidとiOSのディレクトリがあり、それぞれBuildに使うための設定ファイル群が生成される。僕はAndroidをサンプル程度のものしか作ったことがなくて、あまり知識がないのだけど、React Nativeは結構Androidのビルドの仕組みなどの知識を要求してくる感じだった。iOSはまだBuildしてないが、Info.plistなどが生成されてるし、まあ多分同じなんだろうと予想している。
build.gradleにはAndroidのSDKやBuildツールのバージョンが書かれていて、Android Studioをインストールした時に入るSDKのバージョンによってはSDKやBuildツールの対象バージョンをインストールするか、設定ファイル自体を対応するバージョンに書き直す必要がある。React NativeのCLIファイルが作るファイルは全てSDKが23でBuildツールが23.0.1だったので、もしかしたら書き直すことの方が多いかもしれない。多分基本的なことなので、トラブルシューティングにも書いてない。
けどよくよく考えれば、公開するために署名やProfileなどの話は避けて通れないし、どちらにしろBuildに関しては抑えておく必要はありそう。
音声ファイルの置き場所
これも考えれば当たり前なのだけど、AndroidをBuildするときに全部がbundleされるわけじゃなさそうで、音声ファイルが参照できなかった。そこでres/rawに入れたのだけど、ここってディレクトリ管理できないっぽくて、そのまま直下に置いた。assetsとして管理すればディレクトリ管理もできそうなので、Androidマン頼む教えてくれ。自分でドキュメント読めという話かもしれない。
APIの分岐
これはハマったというより悩んだという感じだったのだけど、iOS用のコンポーネントが用意されてたり、Android用のAPIが存在してたりして、一つのソースでやろうと思うとまあそうだよねって思うものの、非常に考えさせられた。当然、個別に用意されてるものを使ったほうが実装が楽だったりで便利そうな感じはすごくしたのだけど、そうすると再利用できる部分はどんどん減っていく。それで行き着く先は、個別に作ったほうがいいんじゃね?ってなりそうだなと思ったんだけど、そんなことないよって実際作ってる人がいれば話を聞いてみたい。
まとめ
無事に音読用の機能を追加できた。とりあえずこれでオフラインでも英語の勉強には困らなそう。人の目を盗んでは一人でブツブツ言うということもできそうだ。どっかの綾瀬はるか似がDuo 3.0やってるって言ってたし、Javascript界隈に詳しいっぽいので、色々ReactとかJavascriptの書き方教えてくれると嬉しい。多分Flutterが音声ファイルを流すためのAPIに再度対応したらFlutterの方に機能追加して、そっちをメインにすると思う。FlutterはGoogleが作ってる新しいOSのFuchsiaのUIレイヤーに採用されるみたいで、コミットも活発だし多分追加してくるだろうと予想してる。
やっていき。
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してた。
まとめ
- 新しい言語をやるときにライブラリを作ると色々学びがあってよい。
- 作れないかもしれないけど作るぞ!!ぐらいの勢いが大事。
競技場に触れたかっただけかな?