こんにちは!Diverse広報担当です!
先日、Diverse Meetup #2となる『Flutter開発者必見!あの有名ライブラリの内部実装を解説』を開催いたしました。 本ブログではイベントの様子をお伝えしております。
今回は、前編・中編に続いて、いよいよラストの「コードリーディング」の内容をお届けいたします!
【前編】「事前知識の確認」と【中編】「widgetとElementの関係〜providerの登場人物」をまだご覧になっていない方はまずこちらをご覧ください。
▼目次
当日の資料と動画を公開中!
当日の資料と前編・中編の動画を公開! 資料の36ページから後編の内容となっておりますが、 コードリーティングは画面を共有しながら解説しておりますので、ぜひ動画の方もご覧ください!!
Diverse Meetup #2 前編の動画はこちら
youtu.be
Diverse Meetup #2 中編の動画はこちら
youtu.be
続・イベントルポ大公開【後編】
前編・中編に続き、いよいよラストの後編です!
▼一緒にコードリーディングをやってみよう!(00:10)
トピックをいくつか用意していたのですが、 今回は「ChangeNotifierProvider のライフサイクルについて」見ていきたいと思います!
ChangeNotifierProvider は lib の src の change_notifer_provider.dart の中にあります。
ChangeNotifierProvider<T> 自体はListenableProvider<T> というのを継承しています。
ChangeNotifierProvider<T> の中身の実態は、 ListenableProvider<T> が ほとんど持っていると思っていただければ良いと思います。
特殊な点としては、_dispose というのを提供していることだけです。 _dispose は、ChangeNotifier の dispose を呼び出すだけのものです。 それを親の ListenableProvider<T> にコンストラクターから渡しています。
ListenableProvider<T> の方を見に行きますと、これもですね ListenableProvider <T> は 18行目から始まって65行目で終わっています。 これもあまり内容がなく、実態は親が持っていそうです。
親は InheritedProvider<T> で ListenableProvider<T> から親に対して渡しているのは _startListening というのがちょっと特殊なくらいです。
_startListeningはここに内容があります。
Value として Listenable を持って addListener をして 戻り値として VoidCallback型で removeListener というのを返しているのでこの戻り値の VoidCallback を呼び出せば Listening が外れるようというそんな関数を返しているようです。
中身でやってることは、 InheritedContext の markNeedNotifyDependents というのを Listener に登録する ということをしています。もう少し中身を追ってみないと詳しいいことはわかりませんが 名前からしておそらく変更が通知されたことを依存しているものに 通知するということを行っているということがわかります。
では、_startListening で渡されている
InheritedContext のことは頭の片隅に置きつつ
ライフサイクルの大まかなところを
追っていきましょう!
▼ChangeNotifierProviderのライフサイクルを追ってみよう!(02:48)
InheritedProvider<T> の中身を見ていきます!
InheritedProvider<T> は長いです。
52行目から始まって168行目まであるので
これが本丸だとということがわかりいただけるかと思います。
ライフサイクルについては _Delegate というのが管理しています。
_CreateInheritedProvider というのが _delegate という引数に代入されているので これが _Delegate だということは想像できるかなと思います。
実際にコードジャンプで飛んでみますと _Delegate を継承していますね。
なので _CreateInheritedProvider<T> の中身を見ていけば どんなライフサイクルを辿っているのかということがわかります。ただ、これが Element からライフサイクルが _Delegate されているのものであるというところを見にいきたい場合は InheritedProvider<T> の中身を見にいく必要があります。
今回は ChangeNotifier のライフサイクルを どんな風にして呼び出しているのか この _Delegate の中身を見ていきたいと思います!
まず create というのがあって これは create<T> 型関数というのは、 BuildContext を引数にとってT型を返すような関数になっています。
update に関してはT 型を返すような引数に BuildContxt と前の値を取る関数なので以前の値をどのように変更して返すかということを指定する関数だったと記憶してます。
先ほど ListenabeProvider<T> と ChangeNotifierProvider<T>で startListening とdispose の関数が特徴という話をしました。これらに注目してライフサイクルだけ追いかけていきましょう!
startListening は StartListening<T>型関数を期待しています。
これは void Function() を返す Function、 そして引数に InheritedContext<T>値である Value をとる というものなので、先ほどの ListenableProvider<T>の _startListening と 型が一致しているということがわかります。
それから dispose の方ですが、 dispose はChangeNotifierProvider<T> の中にあります。 dispose とシグネチャーが一致していることはわかると思います。
これらがどこで呼ばれているのかというのを見ていきましょう!
まず startListening の方からいきますと、頻繁に呼ばれている場所を見つけました。
2番目にある CreateInheritedProviderState<T> の中に飛ぶと、State のライフサイクルの中で ChangeNotifier のインスタンスを生成するということをやっているのがわかります。
_CreateInheritedProviderState<T> の中身を見ていきます。 先ほどの startListening が呼ばれているメソッドがどこなのかというのを調べると Value というゲッターに突き当たりました。
DelegateState の Value というのが呼ばれるとその中で値が生成されて管理されるということになるようです。
_removeListener が null である時に実行されるので既に Listen 済かどうかというのは _removeListner が null か null じゃないかで確認しているようです。
この Value がいつ呼ばれているのかを見に行くと InheritedProviderElement の中身になるので、まずは dispose の方に寄り道して見て行きましょう。
dispose の方はどこで呼ばれているのかというと dispose というメソッドの中で呼ばれているというのがあるのでそこを見にいきます。
こちらは CreateInheritedProvider という Delegate の中身です。それが dispose() というメソッドを持っていて、その中で ChangeNotifier の dispose も呼んでいるという形になっていそうです。
では、これらがどんなライフサイクルで呼ばれているのかというのを見に行ってみましょう!
_DelegateState<T> の Value と dispose の方ですね。 それがどこで呼ばれているのかというのは親クラスの _DelegateState<T, D> をまず見に行きまして、その Value がどこで呼ばれているのかをまず見ていきます。
InheritedProviderScopeElement<T>のメンバーにあるゲッターの Value というのは _DelegateState の Value を呼び出していることがわかります。
なので先ほどの ChangeNotifierProvider<T>に関しては、
CreateInheritedProvider が生成した CreateInheritedProviderState というのがここに入ってくるのでその Value を呼び出して ChangeNotifier をこの中で生成するということをしているようです。
その Value を呼び出している箇所はと言うと、見て行きたいのはこの select というところ中身と、notifyDependentの中身、buildの中身、ということになります。
まず一番上は select というメソッドがあって、その中身でも Value を呼び出して何もしなければ ChangeNotifierProvider<T>を作成するということをやっているようです。
これは SelectContext というエクステンションの中身のようです。 使い方をみると buildContext のエクステンションメソッドなので context.select とすると、そのPersonのnameだけ変更された時にここの build メソッドの中身をリビルドすることが出来ます。
2つ目がこちらのnotifyDependent という InheritedProviderScopeElemen<T>のメソッドです。
notifyDependent は名前からして依存しているものに対して状態を通知すというようなものになっていそうです。
元に飛びますと Flutter のフレームワーク中に出てきました。 これはどのクラスかというのを上に辿って見に行きますと、InheritedElement のメソッドが元になっているようです。
InehritedElement というのは InheritedWidget などで使われている Element にあたるということなので、ここまで来るとFlutter の中身になります。
中身としては依存しているものに対してdidChangeDeoendencie、 依存対象のものが変更されたよ!という通知するメソッドを呼び出すだけです。それを InheritedProvider は継承した上で変更しています。
ライフサイクルだけを追っかけたいので変更通知するメソッドが呼ばれた時に ChangeNotifier が生成されたりするということが分かればとりあえず今回は良しとしましょう!
このあたりはassertion だとかデバッグ用の出力が多かったりするので中身が追いづらいです・・・。
もう1箇所 ChangeNotidfire が生成されるような Value が呼ばれる場所があります。
InheritedProviderScopeElement<T>の build() です。なので、StatefulWidget の State の中の build みたいなものだと思っていただければ良いかと思います。
そこの中でもし InheritedProviderScop Element<T>のオーナーが _lazy というプロパティを抱えていて、それが false だった場合は build メソッドが呼ばれたタイミングで初期化を走らせたりここで値の計算を強制的に行わせると書かれています。
その build のタイミングでもChangeNotifier を生成するということをしているようです。
どんなライフサイクルで ChangeNotifier が生成されるのか、dispose されるのかはだいたい追えたかと思いますが、ご理解いただけましたでしょうか?
資料の関係図(p31 ~p34)を見ながらだとコードジャンプした時もどこに何があるのかと把握しやすいかなと思うので、もしお時間あればご自分でも見ていただけると色々と楽しめると思います。
さいごに
これでDiverse Meetup #2となる『Flutter開発者必見!あの有名ライブラリの内部実装を解説』のイベントルポは終了です!
最後までお付き合いいただきありがとうございました!
▼Diverseの情報発信について
Diverseはこのようなイベントの他にも各所で情報発信を行っています。興味のある方はぜひ覗いてください!