解せぬ日記

雑な話をする

Flutterで英語をやってくためのDuo3.0アプリを作ってみた話

前回、FlutterとDartをしばらくやってみると言ってたこともあって、土日にアリアンデルに籠りながら、Flutterをやっていた。アリアンデルというのはダークソウル3のDLCのことですので、アリアンデルの絵画世界に興味がある人は一緒にやりましょう。

TourとかDart bootstrapとかを眺めたり、実際に動かしながら見ていたわけだけど、途中から眠くなって、アプリでも作りながら困ったらドキュメントに戻ればいいかーといつもの雑さでアプリを作り始めた。 最近、Duo3.0をやってるので、オフラインでもディクテーションとか暗記(覚えれば音読をいつでもできる)とかできるといいよねって思って、Duo3.0アプリにした。

成果

以下、成果になります。

ソースコードも置いとく。

github.com

TL;DR

FlutterのUIはシンプルなものなら割と作りやすいと思う。HTMLっぽさがあってとっつきやすかった。ただその分、オブジェクトを生成しまくってて大丈夫なんかなとも思った。完全にFlutterという言語を書いてる気分で、AndroidiOSのこと知らなくてもそれなりのものは書けそう。

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という言語を書いてる気分で、AndroidiOSのこと知らなくてもそれなりのものは書けそう。 使ってるうちに音出したいなーと思ったんだけど、"Remove sound.dart"というコミットを発見してウッてなってるのが今。

Flutterに興味が出た人はこちらも参考にするといいかもしれない。

terut.hatenablog.com

話は変わるが、この間"やっていき宣言"なるものが行われ、いいなーと思ったので、おっさんだけどやっていくことにした。

yatteiki.fm

異常に怠惰なので気を抜くと気持ちだけ、一発にもならない何かみたいなので終了してしまうのが正直なところではあるが、気負わずやっていき。