サンプルはここを確認するとよいと思う。
https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/material/bottom_app_bar_demo.dart
このサンプルではFABの位置やノッチの有無などをカスタマイズできるようにしている関係でなんだかややこしそうに見えるが、やることは非常に単純。
Scaffold内のbottomNavigationBarにBottomAppBarを渡す Scaffold内のfloatingActionButtonLocationでFABの位置を指定 ノッチの有無はBottomAppBarのhasNotchで指定 必要なメニュー等はBottomAppBar内のchildに追加 これだけでBottomAppBarを表示できる。非常に簡単。 BottomAppBar内のアイテムを複数指定したい場合、childなのでRowでくるむなどの工夫が必要。
flutterの公式リポジトリをクローンしてきて、material demoを動かすとどんな動きか確認することができる。
ちなみに公式リポジトリ内のexampleを動かすにはここを見ればよい。
flutterの初期設定を済ませていることを前提として、手っ取り早くexampleを実行するために必要なことをまとめるとこうなる。
クローンしたあとbinディレクトリにパスを通す(リポジトリ内の最新版flutterを使うため) `flutter update-packages`を実行して各種依存パッケージの解決を行う IntelliJ IDEAを使っているなら`flutter ide-config --overwrite`を実行することでRun Configurations等の設定をしてくれる 後は実行したいexampleをIDEから選んで実行 binディレクトリにパスを通すと、普段使っているflutter(私の環境だと0.3.2)と公式リポジトリのflutter(これ書いてる時点では0.4.4-pre.8)が混じってしまうのだけど、公式リポジトリを触っているときだけ後者を有効にするみたいな設定の仕方ってあるのだろうか。 ありそうな気がするけど私は知らないので、都度パスを追加して対応している。
追記
別にリポジトリをクローンしてこなくてもPlayストアにアプリが公開されていることを知った。 https://play.google.com/store/apps/details?id=io.flutter.demo.gallery 動きを確認するだけならこっちのほうが手っ取り早い。
FlutterでTwitterクライアントを作ってみた。 レイバン製造機になる未来しか見えないので公開したりはしないけれど。 とりあえずマルチアカウント対応とタイムラインの取得を実装して力尽きた。 twitter_loginなどのライブラリがflutter(dart)でも存在しているようだが、そういうのは利用せずに直接APIとやり取りする形で実装した。
使ったライブラリ crypto Twitter APIを利用する署名計算のため cryptoutils 同上 url_launcher ブラウザ立ち上げのため flutter_redux redux_persist_flutter mockito モックテスト Access Tokenの取得はこの記事を参考にして実行した。
Kotlinで書きたい問題 Androidネイティブから入門した身からすると、dartではなくKotlinで書きたいという気持ちでいっぱい。
はじめはセミコロンのつけ忘れが多発し、その次はインスタンスを生成する際にnewを付け忘れるが多発して困ったからという理由が大きかった。newのつけ忘れは、なくても動く場合と、つけないと動かない場合があって、その違いがいまいち分かっていない。 しかも付け忘れて動かないのは、実行しないとわからないのが更に困りものだった。
Kotlinで書きたい理由は、今だとこの2種類の理由から。
data classが使いたい Null許容・非許容を型で表現したい data classについてはdartのissueに上がっているので、そのうち実装されるかもしれない。 Javaで書くことに比べたらdartでのデータオブジェクトの記述はスッキリしている(getter/setterを定義する必要はない)のだが、data classならequalsメソッドをいちいちオーバーライドしなくても済むとか、そういうところが便利だと思うので、早くdartにもdata class来てほしい。
まあそれは来たらいいな程度の気持ちだけど、null許容・非許容を型で表現したいのは結構切実な問題である。 dartはカジュアルにnullが飛んできすぎな気がする。 dartではあらゆるものがオブジェクトなので、intだろうと初期化されていなければnullとなるので、nullと格闘することが多かった。 このあたりは私の実装の仕方が悪いという面も大きいかもしれない。 が、それにしてもnull許容・非許容が型で表現できるKotlinを知っていると、nullで消耗するのがつらい。 Textにnullが渡ってアプリがクラッシュする→どこでnullが渡ったのか把握しきれないとかいうのが多くてね・・・。
dartのasync/await便利 asyncなメソッドを定義してやればそれだけで非同期処理が記述できてしまうのは非常に便利だと思った。 awaitと組み合わせて複数のリクエストを同期的に書けるのはめちゃくちゃ楽だった。 Androidネイティブだと、RxJavaを使って連結させてやるような処理がシンプルに書けるのはよい。 ネットワークを使った処理がシンプルに書けるのはすごい楽だった。
redux アプリ内の状態を管理するのにreduxを使った。
最初はflutter_reduxから導入の仕方を学んだのだが、redux初見の私にとってはこれは悪手だった。 なぜシングルトンで管理しているはずの状態を、わざわざ_ViewModelに移し替えているのか最初は理解できなくて混乱した。 flutter_reduxはredux.dartとflutterの橋渡しをするライブラリなので、そこを切り離して考えないといけないだろう。
dart.redux dart.redux
アプリケーションの状態を1つのクラスに集約する(名前は別になんでもいい。以下利便性のためにAppStateと記述するけど、名前はなんでもいい) 状態クラスはイミュータブルで変更不可にする(重要)
AppStateはアプリケーション内で唯一の存在となるStoreが保持する
状態を変更するのはActionで、ActionはStore経由でdispatchする。dartには型があるので、○○Actionみたいなクラスを作っていく。
Actionと状態の変更を対応付けるのがReducer。
Reducerはネストというか、AppStateが持つ各フィールド(個々のアプリケーションの状態。カウンタの値とか、Twitterクライアントならツイートの一覧とか)とReducerを個別に対応させることもできる。 この場合、AppStateの特定のフィールドの値と、それを変更するActionの対だけをそのReducerで管理すれば良くなるので、見通しが良くなる。
Reducerでは変更する状態とActionの組み合わせを使って新たな状態を返す。
AppStateの変化はStoreからStreamとして流れてくる。このStreamはbroadcast streamなので、状態変更に関心を持つやつが、このStreamをlisten()すれば状態変更をキャッチできる。
MiddlewareはStoreにActionがdispatchされて、そのActionがReducerに渡される前に処理を挟むことができるやつ。例えば流れてくるActionをログに保存するとか、状態をファイルに保存するとか、1つのActionを複数のActionに分割するとか。 今回のTwitterクライアントでは、サインインのアクションをMiddlewareで処理して、その結果からAccess Tokenを更新するActionを発行してアプリ内のAccess Tokenを更新するような処理を行った。
dart.reduxのおさらい
アプリケーションの全状態を保持する`AppState`を用意する `Store`が`AppState`を保持する `Store`経由で`Action`を投げる `Reducer`が`Action`を元に`AppState`を更新する `Store`の`onChange`を`listen()`することで`AppState`の変更を監視する flutter_redux flutter_reduxが担うのは、各WidgetからStoreへアクセスするしくみを提供すること。
`Store`の保持 保持した`Store`へのアクセス `Store`からの変更の`listen()`の隠蔽 たぶんflutter_reduxを使わなくても、`Store`は結局シングルトンなので、直接アクセスするしくみを作ってしまえばそれで済む(はずだし、そっちのがシンプルでわかりやすい気もする)。 一方で、`AppState`の変更はStreamで流れてくるので、この購読と解除をしなければならない。その部分をflutter_reduxは隠蔽してくれるので自分で管理しなくて済む、というところにメリットがあると思われる。 アプリケーションのルートWidgetをStoreProviderにして、Storeを保持する。
子のWidgetではStoreConnectorを通じてStoreにアクセスする。 StoreConnectorはconverterを経由してAppStateをViewModelに変換する。
ViewModelというのは、該当のWidgetを構築するのに必要なだけの状態を別途切り出したやつというイメージ。 状態変更の度にWidgetの更新が走らないようにする役目もある。
状態を持ってるのにStatelessWidgetになるというのがいまいち理解できなかったが、状態を管理しているのはStoreであってこのWidgetではないからだろうか。
データの永続化 redux_persist_flutterは、reduxで管理するアプリの状態をファイルに書き出して永続化するライブラリ。 今回Twitterクライアントをflutterで作っていて、データの永続化をどうすればいいのかいまいち分からなくてとりあえずこれを使った感じである。 別にシリアライズ方法はなんでもいいのだろうけれど、JSONにパースするようになっていたのでそのようにした。 しかしStoreで管理する状態が増えれば増えるほど、JSONへのパース処理を追加するのが大変になって辛くなっていく。
簡単なKey/Valueのデータであれば、shared_preferencesを使えばいいのだろうが、アプリのデータを保存するのには向かない。 自前でファイルに保存するか、Databaseのライブラリを導入して保存するかといったところなのだろうか。 どっちにしろデータオブジェクトとのマッピング処理を自前で実装しないといけないのが面倒くさいところ。
またデータの暗号化もどうしたらいいのやらという感じでよくわからない。
その他雑感 エラーメッセージがわかりにくいところがツライ。エラーの内容は分かるものの、そのエラーが自分の書いたコードのどの部分で発生しているのかを把握するのに大変労力を必要とする。
UIの記述がツライ。 ネストが深くなりすぎてツライ。 これ絶対後からいじれなくなるやつだと思う。
IDEのサポートが受けられるのはめちゃくちゃメリットだと思う。 IntelliJは偉大だ。 無料で使えるCommunity Edditionが使えるのは偉い。 React NativeだとWebStormになると思うので(IntelliJ系IDE使うなら)、そこはメリットだろう。
Flutterを触り始めたのだが、とにかくWidgetのレイアウト変更がツライと感じていた。
AndroidでいうところのViewのレイアウトがコードでゴリゴリ書いていく感じになっているので、インデントが深くなってツライ。Widgetの考え方もAndroidのViewとはちょっと違う(レイアウトに関する設定はレイアウト情報を持つWidgetでくるむとか)
ゼロから作る分にはまだいいのだが、一度作ったレイアウトに対して、「このTextにmargin付け加えたい」となったときがツライ。シンプルなレイアウトならまだいいが、Widgetが何個も登場したり、何回層もネストされてたりすると正直触りたくない。
例えばこんな感じ。
@override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("FriendlyChat"), ), body: new Column( children: <Widget>[ new Flexible( child: new ListView.builder( padding: const EdgeInsets.all(8.0), reverse: true, itemBuilder: (_, index) => _messages[index], itemCount: _messages.length, ), ), new Divider(height: 1.0,), new Container( decoration: new BoxDecoration( color: Theme.of(context).cardColor, ), child: new Text("hoge"), ), ], ), ); } これはまだかわいいものだが、それでもめんどうくさい。
そこでLive Templateを追加して、IntelliJのSurrond With機能を使って簡単にWidgetを囲えるようにしてみた。Flutterのチュートリアルを試したりするのに活躍するのでかなり捗ると思う。
Live Templateを定義 Preference > Editor > Live Templateが定義場所。
定義する場所は別にどこでもいいのだろうけれど、Flutterに関することなのでFlutterのところに追加した。
new Column( children: <Widget>[ $SELECTION$ ] ), Dartファイルに適用されるように指定して、Reformat according to styleにチェックも入れておく。
new Container( child: $SELECTION$ ), childを1つしか取らないWidgetでラップする場合に使うやつもついでに追加。使っているのがColumnとContainerであることに特に意味はない。まあそこは必要に応じてクラス名を書き換えればいいだろうから気にしない。
Live Templateを定義したら、後は囲いたいWidgetを選択した状態で、Surround with機能を呼び出し(私の環境だとcmd+opt+t)、定義したLive Templateを適用すればよい。楽ちん。
Live Templateは初めて活用したので、もっとこうした方がいいよというのがあったら教えて欲しい。
追記:
記事を書き終わった後で、そもそも標準でWidgetをラップする機能が存在していることを知った。ラップしたいWidgetのクラス名にカーソルをあわせた状態で、Intention Actions(Macならopt+enterで出るやつ)を使うとWidgetを別のWidgetでラップすることができる。わざわざLive Templateを追加する必要はなかった・・・。