ntaoo blog

主にDart, Flutter, AngularDartについて書いていきます。ときどき長文。日本語のみ。Twitter: @ntaoo

Flutter 1.0 安定版が発表された (Flutter Live キーノート 概要レポート)

本日Londonで行われているFlutter LiveのキーノートがYoutubeライブ配信されていたので、キーノートの概要を手短にレポートする。

ちなみに、Flutterのプロダクトマネージャーがキーノートと同内容を公式ブログに寄稿しているので、英語を苦にしなければ原文を読めば内容は詳細に網羅されている。

Flutter 1.0: Google’s Portable UI Toolkit

Flutterとは

Flutterは、美しいUIのアプリをシングルコードベースでクロスプラットフォーム開発できるUIツールキット。古めの端末でも高パフォーマンスで動作する。Stateful hot reloadingの恩恵により、状態を維持したままコードの変更が即時に開発中のアプリに反映される。(SmalltalkのLive programming環境に少しだけ近づいてきた)。ネイティブ環境に依存する機能はプラグインを開発して呼び出す。それ以外はUIも含めてすべてDart言語で書く。したがって、UIのマークアップ用の外部DSLは存在しない。

すでにGoogle内部で、Google AdsiOS版とAndroid版がFlutterで開発されていることは有名だろう。(ちなみにWeb版はAngularDartで開発されている。)Googleの外でも、特に中国系の企業が積極的にFlutterを使用していて、世界一多くの開発者とユーザーがいるそうだ。

  • 200万人以上のユーザーがFlutter製アプリを利用
  • 25万人以上の開発者がFlutterでアプリを開発
  • 3000以上のアプリがplay storeに公開
  • GitHubの人気リポジトリで34位

f:id:ntaoo:20181205081625p:plain

本日のカンファレンスのキーノートで、ついに1.0のリリースがアナウンスされた。めでたい。

サードパーティのエコシステム

サードパーティのエコシステムについて、3つのサービスが紹介された。

Flutter 1.0の新機能

f:id:ntaoo:20181205084555p:plain

モバイルネイティブアプリから真のクロスプラットフォームアプリ開発環境へ

Flutterでデスクトップアプリ

Windows, macOS, Linux, ChromeOSのネイティブアプリ対応が公式に宣伝された。(現在はまだ安定版ではなくearly preview段階)

https://github.com/google/flutter-desktop-embedding

FlutterでWebアプリ (Hummingbird)

なんと、WebアプリもFlutterでシングルコードベースで書いてしまおうという野心的なプロジェクトが発表された。 これについてはすでにレポートを書いたので参照されたい。

ntaoo.hatenablog.com

Flutterの開発体験デモ

開発中のアプリの状態を維持したまま、リスタートなしにリアルタイムでコードの変更がアプリに反映されている様がうまくデモされているので、開発者は必見のデモになっている。その他、サードパーティのサービスのデモや、Firebase MLKitを使用した、カメラによるアバターとの表情や動作の同期のデモなど。

所感

Dartが、Webも含めたクライアントサイドのシングルコードベースでの大統一開発環境になろうという構想が現実味を帯びてきた。

この調子でGCPの公式サポートが充実してほしいし、FirebaseやgRPCのDart公式サポートなどその兆しは十分ある。2018年は、Dartはクライアントサイドにフォーカスすると公式に表明されたが、2019年にはサーバーサイドを含めたユニバーサルな言語となる構想が語られてもおかしくないところまで来ていると思う。

Hummingbird: FlutterでWebアプリを作る構想

本日のFlutter Liveキーノートでは、Flutter 1.0 GA化を始め、たくさんのエキサイティングな発表がされたが、その中でも一番の驚きは、このキーノートの最後の、FlutterでWebアプリを開発可能にするという、Hummingbirdの発表だろう。

f:id:ntaoo:20181205065537p:plain
Hummingbird

GAの次の構想として、AndroidiOSに加えて、Windows, macOS, ChromeOS, ラズベリーパイなどのネイティブ環境での統一したUIツールキットとなる構想が発表されたことは予想の範囲内だが、Webアプリに関してはWebブラウザーという独特の環境で制約が強いため、Webアプリまでを対象にする構想がこの日に明らかにされたことは全く予想外だった。

これによりFlutterは、モバイルアプリ用途を超えた、モバイルOSでもデスクトップOSでもWebブラウザーでもシングルコードベースで動作する、完全にポータブルなUIツールキットと再定義された。

情報源

英語を苦にしないなら、原文を読むほうが早い。原文を読もう。

Google’s Flutter toolkit goes beyond mobile with Project Hummingbird

Hummingbird: Building Flutter for the Web

Hummingbirdはまだオープンソースにもなっていないので、手元で試すことはできない。したがって、この記事では、主に2つ目の記事である、GoogleエンジニアによるHummingbirdの解説について、その要点を以下に挙げる。

要点

  • FlutterのDartコードをWeb用にコンパイルする必要がある。
  • 特定のプラットフォームに依存したコード以外のサブセットがコンパイル対象。
  • コンパイル先のWebテクノロジーのサブセットを選定する。
  • シングルコードベースでWebでも動作させたい。
  • そのため、ネイティブアプリではC++で提供しているFlutterエンジンを、Web用にHTMLやCSSのサブセット、CanvasといったWebテクノロジーを利用するものに書き直している。
  • すでに、すべてのWidgetおよびWidget FrameworkをJavaScriptに問題なくコンパイル可能。
  • HTML+CSS+Canvasのアプローチに加え、未来に向けてCSS Paint APIの利用も試みている。
  • FlutterからDartライブラリを呼び出すことはもちろん全く問題ない。FlutterからJavaScriptライブラリを呼び出すには、package:jsdart:jsを利用する。
  • Flutterの安定性とパフォーマンスを重視するため、内部ではCSSのわずかなサブセットのみを使用する。CSSを書くことを避け、ネイティブアプリと同じFlutterコードベースでWebでも動作させる。
  • 既存のWebアプリにFlutterの埋め込むソリューションは調査中。iframeとshadow DOMを考えている。
  • Custom Elements, Angular Components, React ComponentsなどをFlutterに埋め込むソリューションについても、調査中。これらの非FlutterコンポーネントがFlutterのパフォーマンスと正確性に問題を発生させる可能性があるため、調査を継続している。
  • Webの制約により、ネイティブアプリのネイティブの機能を利用したコードも含めてすべてをシングルコードベースにすることは不可能(なので、Web用のプラグイン開発が必要)

現在のステータス

  • 大多数のFlutter Galleryを描画できるだけのWeb Engineをすでに開発済み。
  • Cupertino widgetはまだ移植していないが、Material widgetは移植済み。

所感

Flutter for Webについて発表がされ、ネイティブで動作していたデモアプリが今度はWebブラウザーでヌルヌルと動作していることに衝撃を受けたものの、直後に第一感として設計上の疑問がふつふつと湧いてきて、Webの独特の制約や要求事項にどうやって応えるつもりなのか、どういう割り切りをしたのか、どこまで移植可能なのか興味が尽きなかったので、さしあたってこの記事にて上記の通り要点のみ翻訳した。

詳細は訳していないので、気になる人は原文を読んだり機械翻訳を使って理解したらいいと思う。

デスクトップOSでは、ソーシャルアイデンティティなどを大多数のユーザーはWebブラウザーに保持しているだろうから、デスクトップでもネイティブアプリだけでなくWeb PWAも選択肢として考慮する必要があるのは明らかだろう。インストーラブルPWAも進んでいるので、デスクトップネイティブアプリかPWAのどちらを優先するかは、開発するアプリの特性をよく考えて検討したら良いし、うまくいけばほとんどのコードをシングルコードベースにできるので、アプリの開発コストがさらに劇的に下がりそうだ。

また、Webアプリを開発していて個人的にもっとも厄介だと感じている点は、CSSの調整コスト、アニメーション、モバイル用のジェスチャーサポートなので、これをFlutter WidgetとEngineで完全に隠蔽して触らずにすむようにしてくれるだけて、最高のDeveloper Experienceになりそうだ。

WebassemblyでなくJavaScriptコンパイルする点については、まだWebassemblyはまったく成熟していないし、すでに成熟したJavaScriptコンパイラーがあるので、まったく妥当だろうと思う。

AngularDart

Flutter Webが未来だとしても、AngularDartは現在もうある非常に信頼できる技術なので、Flutter Webが安定するまでは第一の選択肢としてありがたく利用させていただくことになる。AngularDartとFlutterでModelのコードを共有しておき、Hummingbirdが安定したらゆっくりと置き換えていけばいいんじゃないかと思う。その期間は、数年はあるのではないだろうか。

Google I/O 2019で続報がありそうなので、ぜひ現地に行きたくなった。行こう。

Flutterでデスクトップアプリを開発

公式ではなくコミュニティベースのソリューションだが、興味深い記事だったので紹介。

https://medium.com/flutter-community/flutter-on-desktop-a-real-competitor-to-electron-4f049ea6b061

以下、上記記事の要点。

  • Androidエミュレーターを立ち上げないので、開発速度は更に高速。
  • デスクトップアプリなので、当然ウインドウは可変。まるでWebブラウザのような感覚のレスポンシブデザイン。もちろんウインドウの枠をドラッグで自由に変更できる。
  • Hot reloadingとdebuggingも従来どおり可能。
  • RAMの使用量に圧倒的な差。エミュレーターは1GB使うのに対し、こちらは100MB。
  • Androidエミュレーターでもデスクトップのウインドウでも動くChatデモの動画あり。
  • ホバーやカーソルの種類の変化など、マウス前提のデスクトップアプリに必要なWidgetを用意。
  • ほとんどの既存のWidgetはすでにデスクトップでも利用可能はユニバーサルなもの。
  • TargetPlatformでページのデザインを分岐。レスポンシブデザインのbreak pointが少なくともモバイルとデスクトップ用で用意されているようなもの。
  • CursorWidgetなどのデスクトップ用Widgetは、モバイルでは単に無視される。
  • デスクトップ用レイアウトとモバイル用レイアウトの切り替えは、PageLayoutWidgetで行う。
  • Pluginは、デスクトップもサポートしているものならばそのまま使える。

さっそく試そうと思ったが、コードは近日中 (soon) に公開されるとのことなので、期待して待ちたい。

Fuchsia OSですでにFlutterが動いているはずだし、デスクトップアプリ開発も公式に対応を研究していたはずなので、アプリはすべてデスクトップも含めてFlutterで書いてしまって、WebはSPA PWAでなくペライチのランディングページで済ますという方向性の開発も予想される。

Webブラウザで動かすFlutter for WebもGooglerの個人プロジェクトベースで模索されていて、WebのUIなどの要求事項は独特なのでなかなかWidgetの再利用は難しいはずだが、すでにあるAngularDartという信頼できるソリューションに加えて、もしFlutterも選択肢に加わると、かなり贅沢なクライアントアプリ開発環境になるだろう。感慨深い。

DartのMixinについての解説 (Dart 2.1対応)

Mixinに馴染みがない人が多いようなので、解説する。

Mixinとは

Mixinとは、fieldやproperty, slotなどと呼ばれている状態 (state)、およびmethodなどと呼ばれている振る舞い (behavior) の集合を定義し、それをclassに適用して拡張するもの。関数スタイルのプログラミングにおける関数の合成のように、classを合成する。

主に、Classベースのオブジェクト指向言語において、(単一)継承の階層という制約では表現することが難しいデザインには、Mixinを適用する。

主な言語における採用事例

言語レベルで採用していたり(Dart, Scala, Ruby等)、あるいはデザインパターンのひとつとして紹介されていたり(JavaScript, Swift等)する。

Mixinの起源

1990年にはMixinについての論文が存在する。 OOPSLA '90, Mixin based inheritance (pdf)

ちなみに上記論文はDart ver1の言語デザイナーによるもの。 あるLisp方言で概念が紹介され、Strongtalkで初めて実装された。

DartにおけるMixin

Dartは言語レベルでMixinを採用しており、Collectionライブラリ、Flutter framework, AngularDartをはじめ、あらゆるライブラリ、フレームワークで利用されている。

Dartでは、すべてのclassは暗黙的に自身のmixinを宣言しており、mixinとして振る舞うことができる。ただし、このシンプルな仕様には残念ながらいくつかの問題があるとみなされ、Dart 2.1において、mixin宣言専用の構文を用意するなどして仕様が改訂された。

入門

Mixinの宣言

mixin 識別子による宣言

Dart 2.1から、Mixin専用の構文が追加された。これからmixinを定義するコードを書くならば、おおむねこの構文で書けば良い。

mixin M {}

class宣言と同様に、mixin宣言のbodyにmemberを書いていく。( memberとは、constructor、 field、およびmethodを指す。ただし、mixin宣言のbodyにはconstructorを書けない制約がある。将来はこの制約は緩和されるかもしれない。 )

mixin M {
  int anIntField = 42;
  String aStringMethod() => "M's method";
}

class 識別子による暗黙的な宣言

従来の、class宣言での暗黙的なmixinの生成も可能。ただし、残念ながらこの仕様は、mixinの仕様書では、将来は非推奨 (@deprecated) とし、廃止するだろうと記されている。

abstract class M {
  int anIntField = 42;
  String aStringMethod() => "M's method";
}

Mixinの適用

with句でsuper classにmixinを適用する。

class C extends Object with M {}
final c = C();
expect(c is M, true);
expect(c is C, true);

expect(c.anIntField, 42);
expect(c.aStringMethod(), "M's method");

なお、Dart 2.1からは、構文上、extends Objectを省略できるようになった。

class C with M {}

また、これは意味上は、

class C extends M {}

と書いても同じである。これはDart 1では頻出のイディオムだったが、class C with M {}と書けるようになったので、今はそう書けば良いだろう。

Mixinの詳細の解説

前述の通り、Mixinは、fieldおよびmethodの集合を定義し、それをclassに適用して拡張するもの。関数スタイルのプログラミングにおける関数の合成のように、classを合成する。

Mixinは、「super classに」適用され、新しい「匿名の」classを生み出す。これを「Mixin Application」と呼ぶ。

MixinのInterface

このMixin Applicationがされた「匿名の」classを継承したclassは、その結果として、自身のmemberに加えてMixin Applicationのmemberのinterfaceを実装している。

class C extends S with M1, M2, M3 {}

上記の例においては、class Cは、super class Sに、mixinであるM1, M2, M3を適用したmixin applicationであるclassを継承している。

final c = C();
expect(c is M1, true);
expect(c is M2, true);
expect(c is M3, true);
expect(c is S, true);
expect(c is C, true);

isは、objectのclassがinterfaceを(直接的、または間接的に)実装する宣言をしているかを検査する。Dartでは、すべてのclassおよびmixinは、暗黙的に自身のinterfaceを宣言している。

Dartのユニークな特徴として、is検査は、objectのclassではなく、あくまでinterfaceを対象にして検査している。たとえば、ある2つのobjectがあり、それらの内部の実装が異なっても、interfaceが同じならば、両者は区別されない (modulo reflection)。

(* Interfaceは、objectのアクセス可能なmethodのシグネチャの集合である。fieldにはカプセル化の規則に応じて暗黙的に対応するgetter method、setter methodが宣言され、そのmethodを通じてfieldにアクセスする。)

Mixinのmember

Mixinのmemberとは、fieldおよびmethodを指す。 classと同様に、mixinのbodyにmemberを書いていく。

mixin M1 {
  int aM1Field = 42;
  String aM1Method() => "M1 method";
}
mixin M2 {
  int aM2Field = 43;
  String aM2Method() => "M2 method";
}
mixin M3 {
  int aM3Field = 44;
  String aM3Method() => "M3 method";
}
class S {
  int aSField = 45;
  String aSMethod() => "S method";
}

class C extends S with M1, M2, M3 {
  int aCField = 46;
  String aCMethod() => "C method";
}

Mixin Applicationを継承したCには、自身のmemberに加えてMixin Applicationのmemberのinterfaceが実装されている。

expect(c.aM1Field, 42);
expect(c.aM2Field, 43);
expect(c.aM3Field, 44);
expect(c.aSField, 45);
expect(c.aCField, 46);
expect(c.aM1Method(), "M1 method");
expect(c.aM2Method(), "M2 method");
expect(c.aM3Method(), "M3 method");
expect(c.aSMethod(), "S method");
expect(c.aCMethod(), "C method");

Memberの探索順序

複数のmixinに同名のmemberがある場合、どのmixinのものを優先するかには明快な規則がある。

  1. まずは、mixin applicationを継承したclassが探索される。
  2. 次に、後から追加したmixinのmemberが優先して探索される。
  3. 最後に、class階層に沿って探索される。

Linerlization (線形化)

たとえば、以下のコードでは、aMethodがそれぞれのbodyに定義されている。

mixin M1 {
  String aMethod() => "M1 method";
}
mixin M2 {
  String aMethod() => "M2 method";
}
mixin M3 {
  String aMethod() => "M3 method";
}
class S {
  String aMethod() => "SS method";
}
class SS extends S {
  String aMethod() => "SS method";
}

そして、Mixin Applicationを継承したCを以下に定義する。

class C extends SS with M1, M2, M3 {
  String aMethod() => "C method";
}

この場合、methodの探索の順序は、C -> M3 -> M2 -> M1 -> SS -> S -> Objectである。

つまり、

final c = C();
expect(c.aMethod(), "C method");

となる。

もし、CaMethod()が定義されていなければ、expect(c.aMethod(), "M3 method");となる。M3に定義されていなければ、expect(c.aMethod(), "M2 method");と、後から追加したmixinのmemberが優先して探索される。その後、SS, S, Objectと、class階層に沿って探索される。

なお、private memberについてもpublic memberと同様にmixinの対象となる。ただし、libraryを超えてmixinする場合は、private memeberにはアクセスできない。

( 注:Dartカプセル化の単位はlibraryである。Private memberは名前にunderscore prefixがついたものである。アクセス修飾子は存在しない。デフォルトでpublic memberとなる。https://www.dartlang.org/guides/language/language-tour#libraries-and-visibility

Mixin Applicationのclassの名付け

Mixin Applicationのclassを、匿名でなく名付けしたい場合は、以下の構文で可能となる。

class CSuper = S with M1, M2, M3;

上記では、CSuperと名付けた。(body ({}) は不要。)

ここで、S, M1, M2, M3の実装は、Linerlizationのセクションのサンプルコードを再利用することとする。

final cSuper = CSuper();
expect(cSuper.aMethod(), "M3 method");

CSuperは、SM1, M2, M3を適用したmixin applicationなので、最後に適用されたM3のmemberが最も優先して探索される。 (規則通り、Sのmemberの優先度は最も低いことに注意。)

さらに、Mixin Applicationのclassに、さらにmixinを追加することも可能。

mixin M4 {
  String aMethod() => "M4 method";
}

class CSuper2 = CSuper with M4;

CSuper2は、CSuperM4を適用したMixin Application classとなる。

final cSuper2 = CSuper2();
expect(cSuper2.aMethod(), "M4 method");

最後に適用されたM4のmemberが最も優先して探索される。

on句とsuper

mixin宣言におけるon句は、super classの制約を宣言する。

  • on句で宣言されたinterfaceを実装したsuper classでのみこのmixinを適用可能となる。
  • mixinのinstance member methodにおいて、super classのsuper invocationを可能にする。(super.foo()).

この制約を満たさないコードはコンパイル時エラーとなる。また、Static analyzerからエラーが報告される。

mixin M on S {
  String aMethod() {
    print(super.aMethod());
    return 'a M Method.';
  }
}

class S {
  String aMethod() => 'a S Method.';
}

class C extends S with M {}
final c = C();
print(c.aMethod());
// a S Method.
// a M Method.

on句は省略可能であり、on Objectと同じ意味となる。

implements句

class宣言と同様に、mixin宣言でもimplements句でinterfaceを宣言し、静的な型検査およびisによる実行時型検査を利用できる。

Generics

classと同様に、mixinにも型パラメーターを追加できる。

現時点での制約

  • mixinにはconstructorを定義できない。この制約は、仕様書によれば将来は緩和されるかもしれない。
  • mixinは別のmixinをextendすることができない。この制約は、仕様書によれば将来は緩和されるかもしれない。

Static member (A.K.A. class member) は、mixinの対象外。

Mixinはあくまでinstance fieldとinstance methodが対象となる。static member (static field, static method) は、classと同様にmixinのmemberとして定義はでき、instance memberからアクセスはできるが、その宣言によって生成されるmixinには含まれない。Mixinでstatic memberを定義することは稀。

リファレンス

仕様書

https://github.com/dart-lang/language/blob/master/accepted/2.1/super-mixins/feature-specification.md

公式サイトでの紹介

https://www.dartlang.org/articles/language/mixins

その他、個人による解説

https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3

まとめ

  • Mixinは、関数の合成のようにclassを合成する。(単一)継承の階層という制約では表現することが難しいデザインには、Mixinを適用する。
  • Dartでは言語レベルでMixinを採用しているため、安心してそれに依存できる。
  • Mixin Applicationのmember探索順序には明快な規則がある。
  • Dart 2.1において、Dart 1において報告されていた問題を解決するために、classから分離して新たにmixin専用のmixin宣言が採用されるなどして、仕様が改訂された。

フィードバックを歓迎します。コメントかtwitterでいただけるとありがたいです。

Dart 2.1の概要と所感

Dart 2.1がリリースされた。

https://medium.com/dartlang/announcing-dart-2-1-improved-performance-usability-9f55fca6f31a

Flutterからのユーザーフィードバックからの改善が中心という印象。 Dart 2.0において、数年に渡り開発チームがかかりきりになっていた、型安全性を強めた型システムの安定版がリリースされたため、これからはよりマイナーな改善に注力していくと思われる。

Dart 2ではクライアントサイドのユースケースに注力していくとアナウンスされたとおり、WebアプリとNativeアプリ両方の生産性を高めていくことが強調されている。 Flutterは無事離陸できた感があるので、WebアプリとしてAngularDartのパフォーマンスと生産性の魅力がもう少し知られたら良いし、そしてもし取り組まれるならば年単位の大仕事になるだろうが、Web用Flutterの動きに期待したい。

数値リテラルの改善

FlutterユーザーからのFBベースの改善とのこと。

TextStyle(fontSize: 18.0) のdouble型リテラルとして18.0と書かなければstatic errorになっていたが、TextStyle(fontSize: 18)と、18と書いてもdouble型と認識してくれるようになった。

Mixinサポートの改善

Mixinの専用構文として、classの代わりに使用するmixinキーワードが導入された。以前は、classキーワードを用いていくつかの制約のもと、mixin applyできるclassを作成していたが、mixin用に構文が分離された。

mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {}

extends句の代わりに、on句を使用する。上記の例では、FlutterのState classのみがこのmixinを適用する事ができる制約となる。(mixin bodyでは、superでsuper classのmethodを起動できる。super.dispose();など。)

FutureStreamdart:coreから利用可能に

FutureStreamを解決するために、大多数のファイルにいちいちimport 'dart:async';と書かなくてもよくなった。 ただし、dart:asyncがすべてexportされたわけではないので、StreamControllerCompleterを使いたい場合は、いままでどおりdart:asyncのimportが必要。

パフォーマンス向上

In a few edge cases, though, the comprehensive checks added by the new type system caused an undesirable overhead of 20–40%. In Dart 2.1 we’ve greatly reduced the cost of the type checks, both for AOT-compiled code and for code run in the VM with JIT (just-in-time) compilation.

たぶん、Reified Genericsを導入した際のパフォーマンス劣化なのだろうなと想像するが、いくつかのエッジケースでパフォーマンス劣化をかなり緩和したとのこと。 また、dart2jsでもコードサイズを17%、コンパイル時間を15%削減できたとのこと。すでにかなりコンパクトなコードサイズだったので満足していたが、そこからさらにコードサイズを17%削減はすごい改善。

Protocol Buffers

Dartサポートが紹介されている。

https://developers.google.com/protocol-buffers/docs/darttutorial https://github.com/knative/docs/tree/master/serving/samples/helloworld-dart

GoやPythonなみの第一級言語としてのサポートと普及が期待できる。 はやくGCP全体でDartサポートが進んでほしい。

その他バグフィックスなどの改善

  • Flutterにおいて、コンパイル時にエラーとなるべきコードがそうならなかった不具合の修正や、いくつかのうまく動いていなかったLinterがちゃんと動くようになったなど、バグフィックスがされている。
  • dart:htmlの新し目のWebAPI、たとえばServiceWorker APIにかなりのfixが入った。あとで試してみたい。

検討中

https://github.com/dart-lang/language にて言語の改善の議論が公開されている。

普通の型安全な型システムがデフォルトになってしまったし、仕様が膨らんでいって、良くも悪くも普通の言語になっていく印象。

Optional Semicolon

KotlinのようなOptional Semicolonを検討中とのこと。 https://github.com/dart-lang/sdk/issues/30347

かなり議論が紛糾した経緯がある。 https://github.com/dart-lang/sdk/issues/30347

個人的にはStatement separatorに曖昧なルールを導入してしまうのは反対なのだが...非採用になってほしい。 そんな危険なものよりも、finalとvarをoptionalにして、そのかわりにassignemntに、finalには=を、varには:=を使う記法を採用してほしい。

AngularDartでHot Reloadが使えるようになっていた

FlutterのウリのひとつであるHot Reload、この体験がAngularDartでも可能となっていた。WebでもHot Reloading。

dartファイルの編集

f:id:ntaoo:20181020054841g:plain

htmlファイル(angular template)の編集

f:id:ntaoo:20181020054942g:plain f:id:ntaoo:20181020055032g:plain

cssの編集

f:id:ntaoo:20181020055119g:plain

これで、とくにHTMLとCSSの編集がかなり捗るようになる。

--hot-reloadオプションをつけて起動するだけで、その他の設定を変えずに有効になった。

webdev serve --hot-reload

従来のLive Reloadでは、コードの編集からブラウザのリフレッシュを経てのコードの反映までに5秒から10秒かかり、ブラウザのタブがリロードされることでランタイムの状態が一新されていたが、Hot Reloadでは0.5秒から2秒くらいで差分更新され、その他のランタイムの状態が維持される。

ただしまだ不安定な印象。手元のかなり大きなコードベースでは、ランタイムエラーがでたり、なぜかhot-reloadの動作の直後にlive-reloadの動作になりブラウザのタブがリロードされてしまう。

従来のLive Reloadでも便利なので、安定するのを気長に待ちたい。

webdev serve --live-reload

安定したら公式が大々的に宣伝をしそう。

AngularDart - UI Componentのアニメーションを維持したままページ遷移する

やりたいことは以下の動画のとおり。

f:id:ntaoo:20181018064021g:plain

ページ遷移してURLは変わるが、アニメーションは維持する。

モチベーション

Routerでページ遷移すると、対応するComponentが新たに起動する。しかし、上記の動画のように、たとえばTabの遷移に合わせてURLを変更したい場合、Component instanceが新たに起動して状態が一新されると、UI上はアニメーションがぶつりと中断してしまい不格好なUIになってしまう。アニメーションを維持したままページ遷移したい。

ソリューション

Tab1 page, Tab2 pageに対応するpathに紐つけるPageのComponentを、同一のものにする。ここでは、ATabCyclePageとする。 そして、Routerのlife cycle interfaceであるCanReuseを、Componentにimplementする。CanReuseは、Component instanceを再利用するかどうかを指定する。

class ATabCyclePage implements OnActivate, CanReuse {

  int activeTabIndex;

  @override
  void onActivate(RouterState previous, RouterState current) {
    activeTabIndex = _isTab1Page(current.path) ? 0 : 1;
  }

  @override
  Future<bool> canReuse(RouterState current, RouterState next) async {
    // Componentを再利用する条件を記述。たとえばcurrentとnextのpathを比較してUIのTab遷移に対応する状態ならばtrueを返す。
  }
}

Templateのサンプル

<material-tab-panel class="tab-panel" [activeTabIndex]="activeTabIndex" (tabChange)="handleTabChange($event)">
  <material-tab label="Tab 1">
    <template deferredContent>
      <ng-container *ngIf="activeTabIndex != null">
        <material-button raised (trigger)="navigateToTab2Page()">click to Tab 2</material-button>
      </ng-container>
    </template>
  </material-tab>
  <material-tab label="Tab 2">
    <template deferredContent>
      <!--Tab 2 Page-->
    </template>
  </material-tab>
</material-tab-panel>

Tabが変わった際のハンドラ

  void handleTabChange(TabChangeEvent event) {
    if (event.newIndex == 0) {
      _navigateToTab1Page();
    } else if (event.newIndex == 1) {
      navigateToTab2Page();
    } else {
      // throw
    }
  }

  void _navigateToTab1Page() => _router.navigate(RoutePaths.tab1
      .toUrl(parameters: {'id': _router.current.parameters['id']}));
  void navigateToTab2Page() => _router.navigate(RoutePaths.tab2
      .toUrl(parameters: {'id': _router.current.parameters['id']}));

これで、アニメーションを維持したままページ遷移が可能となった。

f:id:ntaoo:20181018064021g:plain