Diverse developer blog

株式会社Diverse(ダイバース) 開発者ブログです。

【中編】イベント『Flutter開発者必見!あの有名ライブラリの内部実装を解説』を公開します【Meetup #2】

こんにちは!Diverse広報担当です!

先日、Diverse Meetup #2となる『Flutter開発者必見!あの有名ライブラリの内部実装を解説』を開催いたしました。 本ブログではイベントの様子をお伝えしております。

f:id:diverse-tech:20210322093104j:plain

今回は、前編の「事前知識の確認」に続いて、中編の「widgetとElementの関係〜providerの登場人物」の内容をお届けいたします。

【前編】の「事前知識の確認」をまだご覧になっていない方はまずこちらをご覧ください。

developer.diverse-inc.com



▼目次


当日の資料と動画を公開中!

当日の資料と動画はこちらをご覧ください。 資料24ページから中編の内容となっております。

youtu.be

続・イベントルポ大公開【中編】

前編に続き、当日のイベントの中編をまとめています。 さっそく行ってみましょう〜!

▼Flutterが管理する3つのツリーとは?(00:00

f:id:diverse-tech:20210322090405p:plain

まず、Flutterが管理する3つのツリーを説明します。 ツリーは、Element というツリーを生成した上でRenderObject というツリーを生成しています。

① Widget
・Widgetはよく知られているもので画面を構成する際に何度も出てくるText、Scaffold、Column、Rowなど
・基本的にはImmutableなオブジェクトで画面上のものの構成情報を表現するためのもの

② Element
・画面に表示されるものの状態を管理するオブジェクト
・iOSのUIViewやAndroidのViewに近い
・ElementもWidgetの構成情報を受けてツリー上の位置にあるオブジェクト(例えばTextが表示するテキストは "Hello World" であるということ)を管理してる

③ RenderObject
・Elementの状態を受けてRenderObjectというツリーがランタイムで自動的に構成されている
・画面上に表示されるものそのものを管理しているオブジェクト
・iOSであればCoreGraphicsのContext、AndroidであればViewのCanvasに近い


この3つのツリーがどのように連動しているのかという話をしてしまうとそれだけで1時間かかってしまうので、Elementというものがいてそれが生き死にをする(ライフサイクル)ということを今回は把握していただけたら良いかと思います。

f:id:diverse-tech:20210322090542p:plain

これだけだけ分かっていただけるとこの先の話は理解できるかと思います。

f:id:diverse-tech:20210322090635p:plain

Flutterが管理する3つのツリーについては、詳しく説明しているブログがいくつか見つかると思いますので、ぜひググってみてください。

f:id:diverse-tech:20210322090724p:plain

Elementが生き死にをする(ライフサイクル)ことを知っておいていただきたい理由は以下となります。

・Providerの中で状態管理のため使われている
・ライフサイクルに合わせたメソッドとか、Elementが抱えている重要なメソッドがある
・StatefulWidgetを使ったことがあるという方は、Stateだと思えばOK
・StatefulWidgetのStateはElementのライフサイクルとほぼ一致している
 - initState( ) はmount( )
 - dispose( ) はunmount( )
 - setState(( ) {})はmarkNeedsBuild( )
 - didChangeDependencies( )やbuild( )はそのまま


▼Elementのライフサイクルについて(03:55

Elementのライフサイクルは大体こんな感じです。

f:id:diverse-tech:20210322090801p:plain

WidgetにはcreateElement( ) というメソッドがありまして、Widget がツリーに追加された段階でcreateElement() というものが呼ばれます。

createElement() で生成された Element は mount() メソッドを呼び出された後、 Element が RenderObject を生成する種類の Element だった場合は、attachRenderObject() を呼び出します。

RenderObjectの追加が必要なければ、Elementが直接アクティブという状態に遷移します。

親の Element から、子の Element はもう使用しないというdeactivateChild() が呼ばれた場合には、deactivate() メソッドを呼び出した後、非アクティブの状態に遷移します。

f:id:diverse-tech:20210322090835p:plain

また Widget のツリーに組み込まれれば、activate() のメソッドが呼ばれて もう一度アクティブの状態に戻ります。

Widget の更新が Element に通知された場合は、update() が呼ばれます。

Element が非アクティブになった後、今のアニメーションフレームの更新終了までの間にアクティブにならなかったらunmount() というメソッドが呼ばれてElementツリーから外れて消去されます。

Element についてはざっくり説明しました。

このあと、Provider の登場人物ということで中身の内部でしか使われていないような非公開のクラスも含めてご紹介したいと思います。

▼Providerの登場人物(05:20

InheritedProvider周りの関係図

f:id:diverse-tech:20210322091204p:plain

皆様よく使われているのが、Provider<T> とか それに類するような ListenableProvider<T>ChangeNotifierProvider<T> とか そのあたりのクラスだと思います。 これらは全て InheritedProvider<T> というのを継承しています。

InheritedProvider<T> は、Delegate<T> というものを継承したインスタンスを抱えていたり、 InheritedProviderScope<T> というインスタンスを子供の Widget として持っていたりします。

f:id:diverse-tech:20210322093454p:plain

あと、FutureProvider<T>StreamProvider<T>ValueListenableProvider<T>という Provider を、もしかしたら使ったことがあるという方もいらっしゃるかもしれません。

これらは DeferredInheritedProvider<T>というのを継承していてこれは InheritedProvider<T> を継承していますのでInheritedProvider<T> だけとりあえず 見ていければいいかなと思っています。

f:id:diverse-tech:20210322091259p:plain

その子供 Widget にあたるInheritedProviderScope<T> というのがInheritedProviderScopeElement<T>というElement を生成しています。

f:id:diverse-tech:20210322091318p:plain

各クラスの説明ですけれども、InheritedProvider は冒頭でお話したInheritedWidget の generic な実装とコメントに書かれています。

所有している Delegate<T> を通じて注入する値の生成、保持や変更監視などを行っていて、_InheritedProviderScope<T> が InheritedWidget を継承しています。

ここでちょっとネタバラシ!
Provider.of<T>() というのは、_InheritedProviderScope<T> を探しているので Provider.of<T>() を使ってProvider を使って注入したものとか、ChangeNotifierProvider<T> を使って注入したもの、さらには StateNotifierProvider<T> というのを使ったことのある方もいるかと思います。 それらも Provider.of<T>() で取得できてしまうのは_InheritedProviderScope<T> というのを探しに行くメソッドを呼んでいるからという仕組みになっているのです。

InheritedProviderScopeElement周りの関係図07:20

こちらは先ほどの図から_InheritedProviderScopeElement だけ取り出して持ってきて他の関係図を書いたような図となります。

f:id:diverse-tech:20210322091416p:plain

_InheritedProviderScopeElement<T> というのは、InheritedContext<T> というインターフェイスを実装しています。 _InheritedProviderScopeElement<T> は、他にも_Dependency<T> というものを持っていたり_DelegateState<T> というものを実装したクラスを抱えていたりします。

_DelegateState<T> はですね、State とついているのでStatefulWidget を思い出される方もいらっしゃるかなと思うんですけどその通りでして、先ほどの画面でもあった _Delegate<T> から生成されます。

f:id:diverse-tech:20210322091604p:plain

InheritedProvider 用の BuildContext みたいなのがInheritedContext です。

BuildContext というのは、Element なAPIは強力なものが多いのでそれを Widget のツリーから直接触られると困るということから公開範囲を狭めたInterface になっています。

それと同じようにして _InheritedProviderScopeElement<T> のAPIの公開範囲を狭めている Interface がInheritedContext<T> です。

そして、_InheritedProviderScopeElement<T> が変更監視の本丸です。

_InheritedProviderScopeElement<T> が Elementで、ライフサイクルがあるため状態を持ってるということになります。なので _IntheritedProviderScopeElement<T> を中心に見ていくと状態の変更監視がどのようにして行われているのかというのがわかります。

f:id:diverse-tech:20210322091629p:plain

_Dependency<T>は Selector というProvider の仲間のようなものがいるんですが、それから渡された aspect というオブジェクトを保持しています。

_SelectorAspect<T> が aspect そのもので、関数の型定義になっています。

_DelegateState<T> を見てみますと、_InheritedProviderScopeElement<T> のライフサイクルイベントを司るのが_DelegateState<T> です。 これが値の生成や保持、デバッグ出力などを行っています。

それから名前の通りSatefulWidget の State に近い役割をもっています。

それを生成するのが _Delegate<T>でState からアクセスする 変更されないようなインスタンスを抱えています。また StatefulWidgetに近いです。

_Delegate周りの関係図09:45

_Delegate<T> というのは愉快な仲間たちがいっぱいまして、 _Delegate<T>_DelegateState<T> を継承したクラスがいっぱい種類があります。

f:id:diverse-tech:20210322091712p:plain

下半分は DeferredInheritedProvider<T> で使われているもので、上半分は InheritedProvider<T> で使われているものです。

一番最初の方に話した通り InheritedProvider<T> を主に見ていきたいので、InheritedProvider<T> に関連する上の方だけ見ていきますと、Create と付いているタイプとValue と付いているタイプの二種類に大きく別れます。

f:id:diverse-tech:20210322091753p:plain

⚫︎ _Createがついているもの
Provider のコンストラクタにcreate というもの(パラメータ)があると思うのですが、それを使用した場合に使われる Delegate<T> です。

f:id:diverse-tech:20210322091830p:plain

⚫︎ _Valueがついているもの
Provider.value という名前付きコンストラクタがあるのですが、それを使用した場合に使われる _Delegate<T> になっています。

f:id:diverse-tech:20210322091904p:plain

やってる事は、Value と付いているものの方が簡単です。 与えられた値をただ保持しているだけなので。Value として与えられた ChangeNotifier とかが更新される事があるのでその監視はしますが、あとはvalue を抱えているぐらいです。

_ValueInheritedProviderState<T> に関してはupdete を実行するぐらいですが、対して Create と付いている方はそこそこ複雑で、create 関数、update 関数、dispose 用のコールバックなどを抱えています。 その State 側は create 関数を実行した結果を保持したりしています。

あと update のタイミングに_Delegate<T> が持っている update 関数を実行するとか、 そのようなことをします。

f:id:diverse-tech:20210322091939p:plain

その他11:30
そのほかSlector<A,S> というものがありまして、 Providerから提供されている値が一定の条件を満たしたような変更をされた時じゃないとWidget のツリーを更新したくないという時に更新条件を絞ることができるものがあります。

Selector は dependOnInheritedElement で比較する関数、aspect というものを渡す役割を持っています。

f:id:diverse-tech:20210322092004p:plain

Consumer というのはいにるはいるんですがただ中で Provider.of<T>() を呼ぶだけの StatelessWidget なのでそんなに難しく考えなくて良いです。Consumer<T> のことは忘れても大丈夫です。

f:id:diverse-tech:20210322092006p:plain

ツリーが3つあるとか、このあたりの話はじっくり時間をかけてコードリーディングをしていくための材料提供だと思っています。 コードを読むための手がかりになると思いますので今すぐわからなくても大丈夫です。




さいごに

最後までお読み頂き有難うございました!

中編のイベントルポはこれで終了です。後編もお楽しみに!


▼次回イベント予告

Diverse Meetup #3が、2021/05/12(水)19:00〜20:00に開催予定!

今回のテーマは『Lookerを使ったデータドリブンなアプローチ』です。

ご興味ある方は奮ってご応募ください!

connpass.com


▼Diverseの情報発信について

Diverseはこのようなイベントの他にも各所で情報発信を行っています。興味のある方はぜひ覗いてください!

www.youtube.com

qiita.com

www.wantedly.com