Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ゆめみの Flutter エンジニア育成方法

Kanta Mori
November 16, 2023

ゆめみの Flutter エンジニア育成方法

Kanta Mori

November 16, 2023
Tweet

More Decks by Kanta Mori

Other Decks in Programming

Transcript

  1. ゆめみの Flutter エンジニア育成⽅法
    森 寛太 / 山﨑 敦史 / 森田 陸斗

    View full-size slide

  2. ⾃⼰紹介
    森寛太
    morikann
    ⼭﨑敦史
    Yamasaki-pan961
    株式会社ゆめみ
    23卒 Flutterエンジニア
    森⽥陸⽃
    trm11tkr

    View full-size slide

  3. Flutter 研修課題[1]

    View full-size slide

  4. Flutter 研修課題のレビュー観点表[2]

    View full-size slide

  5. ⚠「適切」とは

    View full-size slide

  6. 「適切」が使われた観点項⽬
    ⚠ 「適切」とは

    View full-size slide

  7. プログラミングの多様性
    ● 具体的に適切かはそれぞれの実装による
    ● プログラミングの答えは1つではない
    ● レビューで曖昧さが⽣じることがある
    ⚠ 「適切」とは
    熟練者のサポートが必要

    View full-size slide

  8. ⚠ 「適切」とは
    プログラミングの多様性
    プログラミングの多様性

    View full-size slide

  9. レビュー観点表の意味
    ⚠ 「適切」とは
    より良いコードを書く
    ためのヒント
    レビュー項目
    からの学び

    View full-size slide

  10. INDEX
    1. 基礎的なレビュー観点
    2. 各セッションの紹介
    3.終わりに
    Session0: Setup
    Session1: Layout
    Session2: API
    Session3: Lifecycle
    Session4: Mixin
    Session5: Error
    Session6: JSON
    Session7: Serialization
    Session8: StateManagement
    Session9: UnitTest
    Session10: WidgetTest
    Session11: ThreadBlock

    View full-size slide

  11. 基礎的なレビュー観点

    View full-size slide

  12. 全Sessionで共通のレビュー項⽬
    基礎的なレビュー項⽬
    Dart の基礎的な部分
    プログラミングの基礎的な部分
    GitHub の基礎的な部分

    View full-size slide

  13. Dartの基礎的な部分
    ● final と const を適切に利⽤しているか
    ● const をつけ忘れていないか
    ● nullable な 変数を適切に利⽤しているか
    基礎的なレビュー項⽬

    View full-size slide

  14. Dartの基礎的な部分
    ● final と const を適切に利⽤しているか
    ● const をつけ忘れていないか
    ● nullable な 変数を適切に利⽤しているか
    基礎的なレビュー項⽬

    View full-size slide

  15. コンパイル時定数のパフォーマンス
    実⾏速度の⽐較
    const なし
    基礎的なレビュー項⽬

    View full-size slide

  16. コンパイル時定数のパフォーマンス
    実⾏速度の⽐較
    基礎的なレビュー項⽬
    const あり
    基礎的なレビュー項⽬

    View full-size slide

  17. 100 回実⾏した平均
    コンパイル時定数のパフォーマンス
    2.22ms
    0.69ms
    2.281 ms
    0.652 ms
    基礎的なレビュー項⽬
    const なし const あり

    View full-size slide

  18. ハッシュ値の確認
    コンパイル時定数のパフォーマンス
    基礎的なレビュー項⽬

    View full-size slide

  19. ● 実⾏結果
    ハッシュ値 518843245
    164395217
    997697555
    254820803
    318602242
    550760522
    550760522
    550760522
    550760522
    550760522
    コンパイル時定数のパフォーマンス
    基礎的なレビュー項⽬
    const なし const あり

    View full-size slide

  20. GitHub の基礎的な部分
    ● コミット粒度が適切か
    ● コミット⽂が適切か
    ● プルリクエストを提出してレビュアーがレビューし始めた後で、force puth
    を使⽤していないか
    ● レビュー後に UI が変更された場合、PR 添付の画像も変更されているか
    ● ⾃動⽣成ファイルのコミット状況に応じて README や CI が適切に設定され
    ているか
    基礎的なレビュー項⽬

    View full-size slide

  21. プログラミングの基礎的な部分
    ● 保守性
    ○ ハードコーディング
    ○ テスタブルなコード
    ● 安全性
    ○ 型安全
    ○ メモリ安全
    ● 可読性
    ○ 早期リターン
    ○ 説明変数
    基礎的なレビュー項⽬

    View full-size slide

  22. Session0 Setup

    View full-size slide

  23. 課題の準備をする
    ● GitHub Repository の権限を設定
    ○ レビュアー招待
    ○ ワークフロー
    ○ ブランチ保護
    ● 開発環境を構築
    ○ FVM(Flutter Version Management) を利⽤
    ● ⾃動化の設定
    ○ Slack で GitHub 通知の購読設定
    ○ テスト、リントチェック
    ○ レビューアサイン
    Session0: Setup

    View full-size slide

  24. 研修をはじめる
    Session0: Setup

    View full-size slide

  25. 作成されたリポジトリ
    Session0: Setup
    ↓ が設定されたプロジェクトを⽣成
    - CI
    - FVM
    - Lint

    View full-size slide

  26. CI ①
    ↓が設定されたプロジェクトを自動生成
    - CI
    - FVM
    - lint
    Session0: Setup
    analyze, test を実⾏するワークフロー

    View full-size slide

  27. CI ②
    ↓が設定されたプロジェクトを自動生成
    - CI
    - FVM
    - lint
    Session0: Setup
    レビュアーを⾃動アサインするワークフロー

    View full-size slide

  28. FVM (Flutter Version Management)[3]
    Session0: Setup

    View full-size slide

  29. GitHub 通知の購読設定
    Session0: Setup
    Slack に通知が⾶ぶ
    プルリクエスト作成

    View full-size slide

  30. Session1 Layout

    View full-size slide

  31. 天気予報アプリの画⾯レイアウトを構成する
    レイアウト仕様
    ● Placeholder の幅は画⾯の幅の半分
    ● ⻘字と⾚字の Text の幅は Placeholder の幅の半分
    ● Placeholder の⾼さと幅は同じ
    ● Text のパディングや表⽰位置、スタイルの指定
    ● Placeholder と Text を合わせた矩形の中央は画⾯の中
    央と同じ
    ● TextButton の表⽰位置を指定
    Session1: Layout

    View full-size slide

  32. 天気予報アプリの画⾯レイアウトを構成する
    レイアウト仕様
    ● Placeholder の幅は画⾯の幅の半分
    ● ⻘字と⾚字の Text の幅は Placeholder の幅の半分
    ● Placeholder の⾼さと幅は同じ
    ● Text のパディングや表⽰位置、スタイルの指定
    ● Placeholder と Text を合わせた矩形の中央は画⾯の中
    央と同じ
    ● TextButton の表⽰位置を指定
    Session1: Layout

    View full-size slide

  33. チャレンジポイント
    ピンクの四⾓(Placeholder + Text) を画⾯中央におく
    Session1: Layout

    View full-size slide

  34. チャレンジポイント
    ● Placeholder と Text を合わせた矩形の⽔平中央は画⾯
    の中央と同じ
    ● Text と TextButton の隙間は 80 logical pixel
    Session1: Layout

    View full-size slide

  35. 陥りやすい実装の流れ
    1. Center の⼦ Widget に Placeholder を配置
    Session1: Layout

    View full-size slide

  36. 陥りやすい実装の流れ
    1. Placeholder を Center でラップ
    2. Column の⼦ Widget に Placeholder と Text を配置
    Session1: Layout

    View full-size slide

  37. 陥りやすい実装の流れ
    1. Placeholder を Center でラップ
    2. Column の⼦ Widget に Placeholder と Text を配置
    a. MainAxisAlignment.center
    Session1: Layout

    View full-size slide

  38. 陥りやすい実装の流れ
    1. Placeholder を Center でラップ
    2. Column の⼦ Widget に Placeholder と Text を配置
    a. MainAxisAlignment.center
    3. SizedBox(height: 80)
    SizedBox
    Session1: Layout

    View full-size slide

  39. 陥りやすい実装の流れ
    1. Placeholder を Center でラップ
    2. Column の⼦ Widget に Placeholder と Text を配置
    a. MainAxisAlignment.center
    3. SizedBox(height: 80)
    4. TextButton を配置
    Session1: Layout

    View full-size slide

  40. 陥りやすい実装の流れ
    1. Placeholder を Center でラップ
    2. Column の⼦ Widget に Placeholder と Text を配置
    a. MainAxisAlignment.center
    3. SizedBox(height: 80)
    4. TextButton を配置
    Session1: Layout

    View full-size slide

  41. レビュー観点
    ● 縦画⾯固定を適切に設定しているか
    ● AspectRatio を使⽤しているか
    ● MediaQuery の代わりに FractionallySizedBox を使⽤しているか
    ● Expanded と Flexible を使い分けているか
    ● 不必要に Container を利⽤していないか
    ● Theme を適宜利⽤しているか
    ● Column を余分に利⽤していないか
    Session1: Layout

    View full-size slide

  42. レビュー観点
    ● 縦画⾯固定を適切に設定しているか
    ● AspectRatio を使⽤しているか
    ● MediaQuery の代わりに FractionallySizedBox を使⽤しているか
    ● Expanded と Flexible を使い分けているか
    ● 不必要に Container を利⽤していないか
    ● Theme を適宜利⽤しているか
    ● Column を余分に利⽤していないか
    Session1: Layout

    View full-size slide

  43. AspectRatio を使⽤しているか
    正⽅形ということが
    わかりやすくなり
    可読性が向上する
    Session1: Layout

    View full-size slide

  44. MediaQuery の代わりに FractionallySizedBox を使⽤している

    const が修飾可能となり
    パフォーマンスが向上
    Session1: Layout

    View full-size slide

  45. Session1: Layout
    不⽤意に Container を利⽤していないか

    View full-size slide

  46. Session1: Layout
    不⽤意に Container を利⽤していないか
    const 付与

    View full-size slide

  47. Session1: Layout
    不⽤意に Container を利⽤していないか
    const 付与

    View full-size slide

  48. Session2 API

    View full-size slide

  49. Session2 課題
    yumemi_weather[4] の API を使⽤して、
    天気予報を取得する
    ● Reload ボタンをタップして天気予報を取得する
    ● 天気予報を画⾯に表⽰する
    Session2: API

    View full-size slide

  50. 作業の流れ
    ①SVG画像を表⽰できるようにする
    pubspec.yaml
    Session2: API
    assets
    flutter_svg

    View full-size slide

  51. 作業の流れ
    ②YumemiWeather の fetchSimpleWeather() から天気⽂字列を取得する
    Session2: API
    fetchSimpleWeather()
    “sunny”
    YumemiWeather

    View full-size slide

  52. 作業の流れ
    ③取得した天気に対応したSVG画像を表⽰する
    Session2: API

    View full-size slide

  53. レビュー観点
    ● pubspec.yaml に画像を追加する際、ディレクトリで指定しているか
    ● YumemiWeather() を build 関数内でインスタンス化していないか
    ● API の使⽤箇所でエラーハンドリングしているか
    ● 天気の種類をenumで扱っているか
    ● Enum.values.byName() を使⽤していないか
    ● テストのことを考えて、YumemiWeather のインスタンスを DI しているか
    ● アセットの指定間違い防⽌を考慮できているか
    Session2: API

    View full-size slide

  54. レビュー観点
    ● pubspec.yaml に画像を追加する際、ディレクトリで指定しているか
    ● YumemiWeather() を build 関数内でインスタンス化していないか
    ● API の使⽤箇所でエラーハンドリングしているか
    ● 天気の種類をenumで扱っているか
    ● Enum.values.byName() を使⽤していないか
    ● テストのことを考えて、YumemiWeather のインスタンスを DI しているか
    ● アセットの指定間違い防⽌を考慮できているか
    Session2: API

    View full-size slide

  55. レビュー観点
    ● pubspec.yaml に画像を追加する際、ディレクトリで指定しているか
    ● YumemiWeather() を build 関数内でインスタンス化していないか
    ● API の使⽤箇所でエラーハンドリングしているか
    ● 天気の種類をenumで扱っているか
    ● Enum.values.byName() を使⽤していないか
    ● テストのことを考えて、YumemiWeather のインスタンスを DI しているか
    ● アセットの指定間違い防⽌を考慮できているか
    Session2: API

    View full-size slide

  56. fetchSimpleWeather() は String型を返す
    Enum型で扱う=>型安全になる
    タイプミスやスペルミスなどのエラーを防げる
    レビュー観点
    Session2: API

    View full-size slide

  57. レビュー観点
    ● pubspec.yaml に画像を追加する際、ディレクトリで指定しているか
    ● YumemiWeather() を build 関数内でインスタンス化していないか
    ● API の使⽤箇所でエラーハンドリングしているか
    ● WeatherCondition の enum を作成しているか
    ● Enum.values.byName() を使⽤していないか
    ● テストのことを考えて、YumemiWeather のインスタンスを DI しているか
    ● アセットの指定間違い防⽌を考慮できているか
    Session2: API

    View full-size slide

  58. Errorの扱い⽅
    Error は コードのバグを⽰すのでキャッチするべきではない[4]
    Session2: API
    Enum.values.byName(String)
    Enumにない
    文字列
    ArgumentError

    View full-size slide

  59. Enum.values 拡張関数
    Session2: API
    string → Enum?
    拡張関数を実装する

    View full-size slide

  60. Session3 Lifecycle

    View full-size slide

  61. StatefulWidget のライフサイクル
    課題詳細
    ● StatefulWidget で構築された新しい画⾯を追加する
    ● 新しい画⾯の背景⾊は Colors.green に設定する
    ● アプリ起動時に新しい画⾯に遷移する
    ● 新しい画⾯が表⽰されたら、0.5秒後に天気の画⾯に遷
    移する
    ● 前回まで作っていた画⾯の Close ボタンをタップすると
    新しい画⾯に戻る
    Session3: Lifecycle

    View full-size slide

  62. StatefulWidget のライフサイクル
    課題詳細
    ● StatefulWidget で構築された新しい画⾯を追加する
    ● 新しい画⾯の背景⾊は Colors.green に設定する
    ● アプリ起動時に新しい画⾯に遷移する
    ● 新しい画⾯が表⽰されたら、0.5秒後に天気の画⾯に遷
    移する
    ● 前回まで作っていた画⾯の Close ボタンをタップすると
    新しい画⾯に戻る
    Session3: Lifecycle

    View full-size slide

  63. State のライフサイクル
    ライフサイクルメソッド
    ● StatefulWidget.createState
    ● State
    ○ initState
    ○ dispose
    ○ setState
    ○ build
    ○ didChangeDependncies
    ○ didUpdateWidget
    Session3: Lifecycle
    ライフサイクルの状態

    View full-size slide

  64. State のライフサイクル
    Session3: Lifecycle
    created
    initialized
    initState()
    ● State オブジェクトが生成された状態
    ● initState() が呼び出される
    ● State オブジェクトが Widget tree に初めて
    挿入される時に一度だけ呼び出される
    ● initState() が呼び出された状態
    ● didChangeDependencies() が呼び出される
    ● ビルドの準備はできていない

    View full-size slide

  65. レビュー観点
    ● build メソッド直下で画面遷移処理を書いていないか
    ● sleep を使っていないか
    ● mounted のチェックをしているか
    ● 初期画面と天気画面の Widget を分割しているか
    ● iOS・Android のスワイプバックを考慮しているか
    Session3: Lifecycle

    View full-size slide

  66. レビュー観点
    ● build メソッド直下で画面遷移処理を書いていないか
    ● sleep を使っていないか
    ● mounted のチェックをしているか
    ● 初期画面と天気画面の Widget を分割しているか
    ● iOS・Android のスワイプバックを考慮しているか
    Session3: Lifecycle

    View full-size slide

  67. sleep を使⽤していないか
    Session3: Lifecycle
    ● sleep
    ○ UI 処理をブロック
    ● Future.delayed
    ○ UI 処理をブロックしない

    View full-size slide

  68. mounted のチェックをしているか
    Session3: Lifecycle
    ● ⾮同期処理中に dispose() が呼び出
    される可能性
    ● mounted により確認

    View full-size slide

  69. Session4 Mixin

    View full-size slide

  70. Mixin パターン
    ● AutomaticKeepAliveClientMixin[5]
    ○ AutomaticKeepAlive のクライアントのための便利なメソッドを持つ Mixin
    ○ Stateサブクラスで使⽤する
    ● TextSelectionDelegate[6]
    ○ ツールバーやショートカットキーで選択範囲を操作するための Mixin
    ● after_layout[7]
    ○ Widget の最初のレイアウトが実⾏された後、つまり最初の frame が表⽰された
    後にコードを実⾏する機能をもたらす Mixin
    Session4: Mixin

    View full-size slide

  71. Session4 課題
    ● Session3 で作成した ↓ の処理を after_layout のような Mixin を使って書き直す
    新しい画⾯が表⽰されたら、0.5 秒後に前回まで作っていた画⾯に遷移する
    Session4: Mixin

    View full-size slide

  72. Mixin を利⽤したサンプルコード
    Session4: Mixin
    定義クラス

    View full-size slide

  73. Mixin を利⽤したサンプルコード
    Session4: Mixin
    Mixin 利⽤クラス

    View full-size slide

  74. レビュー観点
    ● Mixin で定義されているメソッドは abstract method になっているか
    Session4: Mixin

    View full-size slide

  75. レビュー観点
    Session4: Mixin
    Mixin で定義されているメソッドは abstract method になっているか
    今回の仕様では
    適さない

    View full-size slide

  76. Session4: Mixin
    レビュー観点
    Mixin で定義されているメソッドは abstract method になっているか
    abstract method
    が適切

    View full-size slide

  77. Session5 Error

    View full-size slide

  78. API のエラーハンドリング
    ● yumemi_weather の API を捕捉してダイアログを表⽰
    ○ 使⽤する API をエラーを投げる API に置き換える
    ○ API のエラーを補⾜して、エラーの内容に応じて
    AlertDialog でメッセージを表⽰する
    ○ AlertDialog の OK ボタンをタップすると、ダイア
    ログを閉じる
    Session5: Error

    View full-size slide

  79. fetchThrownsWeather
    Session5: Error
    確率的にエラーが投げられる

    View full-size slide

  80. レビュー観点
    ● エラーの内容によってメッセージを分けているか
    ● クリックイベントをメソッドとして切り出しているか
    ● Dialog をコンポーネントとして抽出しているか
    ● エラーハンドリングが公式ドキュメントに従っているか
    Session5: Error

    View full-size slide

  81. レビュー観点
    ● エラーの内容によってメッセージを分けているか
    ● クリックイベントをメソッドとして切り出しているか
    ● Dialog をコンポーネントとして抽出しているか
    ● エラーハンドリングが公式ドキュメントに従っているか
    Session5: Error

    View full-size slide

  82. ● ネットワークエラー
    ● API レスポンスエラー
    ● 認証エラー
    ● リクエスト制限エラー
    ● 不明なエラー
    Session5: Error
    ⚠エラーが
    発生しました
    OK
    エラーの内容によってメッセージを分けているか

    View full-size slide

  83. ● ネットワークエラー
    ● API レスポンスエラー
    ● 認証エラー
    ● リクエスト制限エラー
    ● 不明なエラー
    Session5: Error
    ⚠エラーが
    発生しました
    エラーの内容によってメッセージを分けているか
    ユーザ体験が低下
    する可能性が⾼い
    OK

    View full-size slide

  84. ● ネットワークエラー ‧‧‧ ネットワークに接続されていません
    ● API レスポンスエラー ‧‧‧ サービスに⼀時的な問題が発⽣しています
    ● 認証エラー ‧‧‧‧‧‧‧‧ ユーザ名またはパスワードが正しくありません
    ● リクエスト制限エラー ‧‧‧ 要求数の制限に達しました
    ● 不明なエラー ‧‧‧‧‧‧‧不明なエラーが発⽣しました
    Session5: Error
    エラーの内容によってメッセージを分けているか

    View full-size slide

  85. エラーハンドリングが公式ドキュメント[8]に従っているか
    ● on 句を使⽤せずの catch は避けるべきである
    ( AVOID catches without on clauses )
    ● on 句なしの catch で捕捉したエラーを捨ててはならない
    ( DON’T discard errors from catches without on clauses )
    ● プログラム上のエラーについてのみ、Error を実装したオブジェクトを投げるべきであ

    ( DO throw objects that implement Error only for programmatic errors )
    ● Error またはそれを実装した型を明⽰的に catch することは避けるべきである
    ( DON’T explicitly catch Error or types that implement it )
    ● 捕捉した例外を再スローする際には rethrow を使⽤するべきである
    ( DO use rethrow to rethrow a caught exception )
    Session5: Error

    View full-size slide

  86. Session6 JSON

    View full-size slide

  87. Json をあつかう
    Session6: Json
    Request
    Response

    View full-size slide

  88. Session6 課題
    ● 使用する API を fetchThrowsWeather() から fetchWeather() に変更する
    ● API から受け取った天気状況・最低気温・最高気温を画面に表示する
    Session6: Json

    View full-size slide

  89. 定番の外部パッケージ
    Session6: Json
    [9]
    [10]

    View full-size slide

  90. 定番の外部パッケージ
    Session6: Json
    あえてここでは⾃前で JSON の
    パース処理を実装して欲しい...!

    View full-size slide

  91. 定番の外部パッケージ
    Session6: Json
    Session7 で外部パッケージを使⽤
    し、その便利性を実感してほしい
    あえてここでは⾃前で JSON の
    パース処理を実装して欲しい...!

    View full-size slide

  92. レビュー観点
    ● ローカル変数の型昇格を適宜利⽤できているか
    ● 全ての例外ケースを考慮して、JSON 変換処理が書けているか
    Session6: Json

    View full-size slide

  93. レビュー観点
    ● ローカル変数の型昇格を適宜利⽤できているか
    ● 全ての例外ケースを考慮して、JSON 変換処理が書けているか
    Session6: Json

    View full-size slide

  94. 全ての例外ケースを考慮して、JSON 変換処理が書けているか
    Session6: Json
    不正なレスポンスに対して考慮不足な実装の一部

    View full-size slide

  95. 全ての例外ケースを考慮して、JSON 変換処理が書けているか
    Session6: Json
    不正なレスポンスに対して考慮不足な実装の一部

    View full-size slide

  96. Session6: Json
    不正なレスポンスを考慮した実装の一部
    全ての例外ケースを考慮して、JSON 変換処理が書けているか

    View full-size slide

  97. Session7 Serialization

    View full-size slide

  98. Session7 課題
    Session7: Serialization
    ● コード⽣成によるJsonパースを実装
    ● build.yaml による設定を学ぶ

    View full-size slide

  99. レビュー観点
    ● build.yaml の設定が正しくできているか
    ○ コード⽣成 ファイルの lint 対応ができているか
    ○ デフォルト値のものを記載しない
    ○ checked: true にしているか
    ○ field_rename: snake をつけているか
    Session7: Serialization

    View full-size slide

  100. レビュー観点
    ● build.yaml の設定が正しくできているか
    ○ コード⽣成 ファイルの lint 対応ができているか
    ○ デフォルト値のものを記載しない
    ○ checked: true にしているか
    ○ field_rename: snake をつけているか
    Session7: Serialization

    View full-size slide

  101. コード⽣成ファイルの Lint 無効化
    Session7: Serialization
    Customizing static analysis | Dart [11]
    build.yaml

    View full-size slide

  102. コード⽣成ファイルの Lint 無効化
    Session7: Serialization
    build.yaml
    Customizing static analysis | Dart [11]

    View full-size slide

  103. コード⽣成ファイルの Lint 無効化
    Session7: Serialization
    build.yaml
    Customizing static analysis | Dart [11]

    View full-size slide

  104. コード⽣成ファイルの Lint 無効化
    Session7: Serialization
    analysis_options.yaml
    コンパイルに関わるエラーも警告されなくなる

    View full-size slide

  105. Session8 StateManagement

    View full-size slide

  106. 状態管理
    Session8: StateManagement
    InheritedWidget
    setState

    View full-size slide

  107. 状態管理
    Session8: StateManagement
    InheritedWidget
    setState
    状態を扱う階層が深くなる
    コード量の増加で可読性が低下する

    View full-size slide

  108. 外部パッケージ(状態管理)
    Session8: StateManagement
    Riverpod
    Provier
    Redux
    BLoC
    GetX
    GetIt

    View full-size slide

  109. 外部パッケージ(状態管理)
    Session8: StateManagement
    Riverpod
    Provier
    Redux
    BLoC
    GetX
    GetIt

    View full-size slide

  110. Session8 課題
    ● Riverpod[11] を導⼊して、天気予報画⾯の状態管理を⾒直す
    ● アーキテクチャを⾒直し、ARCHITECTURE.md に記載する
    Session8: StateManagement

    View full-size slide

  111. レビュー観点(設計)
    ● 適切に責務分割できているか
    ● テストがしやすい実装‧構成になっているか
    ● アーキテクチャ要素やProviderが循環依存していないか
    ● ARCHITECTURE.md はほとんど変更が不要な記載⽅法になっているか
    Session8: StateManagement

    View full-size slide

  112. レビュー観点(設計)
    ● 適切に責務分割できているか
    ● テストがしやすい実装‧構成になっているか
    ● アーキテクチャ要素やProviderが循環依存していないか
    ● ARCHITECTURE.md はほとんど変更が不要な記載⽅法になっているか
    Session8: StateManagement

    View full-size slide

  113. テストがしやすい = 疎結合になり変更容易性が⾼い
    確認項⽬(例)
    ● どこをテストするか考えられているか
    ● その部分のテストが適切に⾏える構成になっているか
    ○ Http クライアントが DI できるようになっているかなど
    Session8: StateManagement

    View full-size slide

  114. レビュー観点(Riverpod)
    ● build 関数直下で ref.read していないか
    ● クリックイベントで ref.watch を使っていないか
    ● .autoDispose を適切に設定しているか
    ● Provider の依存関係図を表⽰している場合、依存関係図を⾃動⽣成できるようにして
    いるか
    Session8: StateManagement

    View full-size slide

  115. レビュー観点(Riverpod)
    ● build 関数直下で ref.read していないか
    ● クリックイベントで ref.watch を使っていないか
    ● .autoDispose を適切に設定しているか
    ● Provider の依存関係図を表⽰している場合、依存関係図を⾃動⽣成できるようにして
    いるか(Good/FYI)
    Session8: StateManagement

    View full-size slide

  116. riverpod_graph[12]
    Session8: StateManagement

    View full-size slide

  117. Session9 UnitTest

    View full-size slide

  118. Unit tests を書く
    Flutter のテストは次の3つに分類される。
    ● Unit tests
    ● Widget tests
    ● Integration tests
    Session9: UnitTest

    View full-size slide

  119. Unit tests を書く
    Flutter のテストは次の3つに分類される
    ● Unit tests
    ● Widget tests
    ● Integration tests
    Unit tests は1つの関数、メソッド、クラスの動作を確認するのに便利
    また、他のテストより依存関係を少なくすることができるため、実装やメンテナンスコスト
    を低く抑えることができる
    Session9: UnitTest

    View full-size slide

  120. Session9 課題
    ● yumemi_weather API の呼び出しから Widget へ通知する部分までの
    Unit tests を書く
    ○ 依存しているものが、成功‧失敗するケースも網羅する
    ● 余⼒があれば、JSON のエンコード‧デコードの Unit tests も書く
    ※ テストコードを書くにあたって、依存関係を⾒直すなどのリファクタリングを⾏っても問
    題ない
    Session9: UnitTest

    View full-size slide

  121. 依存先をモックする
    ● 天気取得APIをモックすることで、様々なテストケースに柔軟かつ簡単に対応できる
    ● Mockito などパッケージを使うことでモックに利⽤するコードを⽣成できます。
    Session9: UnitTest

    View full-size slide

  122. テスト対象
    Session9: UnitTest

    View full-size slide

  123. DI(Dependency Injection)
    YumemiWeather に依存
    Session9: UnitTest

    View full-size slide

  124. Client のモック⽣成
    @GenerateNiceMocks で
    依存先を指定
    build_runner を実⾏する
    $ flutter pub run build_runner build
    Session9: UnitTest

    View full-size slide

  125. ⽣成されたモックファイル
    weather_datastore_test.mocks.dart
    Session9: UnitTest

    View full-size slide

  126. WeatherDatastore のテスト
    Mock したインスタンスを渡す
    Session9: UnitTest

    View full-size slide

  127. WeatherDatastore のテスト
    mockYumemiWeather.fetchWeather の返す値を固定
    Session9: UnitTest

    View full-size slide

  128. WeatherDatastore のテスト
    mockYumemiWeather.fetchWeather の返す値を固定
    前提条件を明確にした状態で
    WeatherDatastore の動作をテストすることが可能
    Session9: UnitTest

    View full-size slide

  129. WeatherDatastore のテスト
    テスト例
    Session9: UnitTest

    View full-size slide

  130. レビュー観点
    ● テストしやすい構成にリファクタリングした際に riverpod_graph を更新しているか
    ● @visibleForTesting を適宜利⽤できているか
    ● 不必要な group を作成していないか
    ● テスト実⾏後に ProviderContainer を dispose しているか
    ● テストダブルを適切に使えているか
    ● テストダブルを利⽤した関数を呼び出す際に any を適宜利⽤しているか
    ● 失敗ケースのテストも書いているか
    ● 例外の場合 thenThrow を使⽤しているか
    ● テストの description が適切な表現になっているか
    Session9: UnitTest

    View full-size slide

  131. Session10 WidgetTest

    View full-size slide

  132. Widget test を書く
    Flutter のテストは次の3つに分類される
    ● Unit tests
    ● Widget tests
    ● Integration tests
    Widget Test はUI が期待通りに表⽰され、動作することを確認するのに便利
    Unit Test より依存が多くなり、Widget のライフサイクルを適切に再現するための専⽤のテ
    スト環境が必要
    Session10: WidgetTest

    View full-size slide

  133. Session10 課題
    ● 次の Widget Test を書く
    ○ 特定の条件で、天気予報画⾯に
    ■ 晴れの画像が表⽰されること
    ■ 曇りの画像が表⽰されること
    ■ ⾬の画像が表⽰されること
    ■ 最⾼気温が表⽰されること
    ■ 最低気温が表⽰されること
    ■ ダイアログが表⽰され、特定のメッセージが表⽰されること
    Session10: WidgetTest

    View full-size slide

  134. レビュー観点
    ● デバイスサイズ変更の意図をコメントしているか
    ● デバイスサイズ変更の後処理(tearDown)をしているか
    ● コンポーネントで完結するテストに、画⾯遷移など他の要素を含めていないか
    ● @visibleForTesting を適宜利⽤できているか
    ● 複数のプラットフォームや複数のスクリーンサイズをテストする時は TestVariant を活
    ⽤しているか
    Session10: WidgetTest

    View full-size slide

  135. レビュー観点
    ● デバイスサイズ変更の意図をコメントしているか
    ● デバイスサイズ変更の後処理(tearDown)をしているか
    ● コンポーネントで完結するテストに、画⾯遷移など他の要素を含めていないか
    ● @visibleForTesting を適宜利⽤できているか
    ● 複数のプラットフォームや複数のスクリーンサイズをテストする時は TestVariant を活
    ⽤しているか
    Session10: WidgetTest

    View full-size slide

  136. コンポーネントで完結するテストに、画⾯遷移など他の要素を含
    めていないか
    テストケース例:
     天気画⾯が初期状態の時、Placeholderが表⽰される
    Session10: WidgetTest

    View full-size slide

  137. 不適切な実装
    ● 初期状態でPlaceholder が表⽰されているかを検証
    Session10: WidgetTest

    View full-size slide

  138. Session10: WidgetTest
    App LaunchPage WeatherPage
    アプリ起動時の動作

    View full-size slide

  139. 不適切な実装
    ● 初期状態でPlaceholder が表⽰されているかを検証
    Session10: WidgetTest

    View full-size slide

  140. 適切な実装
    ● 初期状態でPlaceholder が表⽰されているかを検証
    Session10: WidgetTest

    View full-size slide

  141. レビュー観点
    ● private メソッドはテストから参照できない
    ● テストのために public に変更すると、外部から参照できてしまう
    Session10: WidgetTest
    @visibleForTesting を適宜利⽤できているか
    @visibleForTesting を付与すると
    test フォルダからのみの参照を許可

    View full-size slide

  142. @visibleForTesting
    Session10: WidgetTest
    外部から参照

    View full-size slide

  143. @visibleForTesting
    Session10: WidgetTest
    外部から参照

    View full-size slide

  144. Session11 ThreadBlock

    View full-size slide

  145. スレッドブロック
    時間のかかる処理を扱ってみよう。
    ● 使⽤する API を fetchWeather() から
    syncFetchWeather() に変更する
    ● API の処理が戻るまで CircularProgressIndicator を
    表⽰する
    Session11: ThreadBlock

    View full-size slide

  146. シングルスレッド‧イベントループ
    ● Dart の実⾏モデルはシングルスレッド‧イベントループ
    ○ スレッドをブロックしてしまうような処理の場合は、isolate を⽤いた並
    列処理を⾏う必要がある
    Session11: ThreadBlock
    Flutter で Isolate を用いた並列処理をするべきシーンとそのやり方[14]
    画⾯がフリーズし、ユーザー体験を損ねる
    そうしないと...

    View full-size slide

  147. syncFetchWeather の内部実装
    Session11: ThreadBlock

    View full-size slide

  148. syncFetchWeather の内部実装
    Session11: ThreadBlock
    2秒間 スレッドをブロック

    View full-size slide

  149. syncFetchWeather の内部実装
    Session11: ThreadBlock
    2秒間 スレッドをブロック
    isolate の出番

    View full-size slide

  150. isolate をラップした compute 関数
    isolate を直接扱うとコードが煩雑になるため、それをラップした compute 関数
    で済む場合はそれを使うのが良い
    Session11: ThreadBlock

    View full-size slide

  151. isolate をラップした compute 関数
    Session11: ThreadBlock

    View full-size slide

  152. レビュー観点
    ● isolate で扱うべき処理について (FYI)
    ● syncFetchWeather() への変更に伴い、ARCHITECTURE.md、テストの修正を⾏ってい
    るか
    Session11: ThreadBlock

    View full-size slide

  153. isolate で扱うべき処理について
    ● スレッドをブロックする処理
    ○ 画像、⾳声処理など、CPU 負荷が⾼い処理
    ○ ⼤規模な JSON データの変換処理
    Session11: ThreadBlock
    本セッションの JSON 変換処理は重たくないので
    やらなくてもOK

    View full-size slide

  154. 是⾮ Flutter 研修課題、観点表を
    ご活⽤下さい!

    View full-size slide

  155. ご清聴ありがとうございました!

    View full-size slide

  156. 参考
    Resources:
    [1] https://github.com/yumemi-inc/flutter-training-template
    [2] https://notion.yumemi.co.jp/flutter%E7%A0%94%E4%BF%AE%E8%AA%B2
    %E9%A1%8C%E3%81%AE%E3%83%AC%E3%83%93%E3%83%A5%E3%83
    %BC%E8%A6%B3%E7%82%B9%E8%A1%A8
    [3] https://fvm.app/
    [4] https://yumemi-inc.github.io/flutter-training-template/
    [5] https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixi
    n.html
    [6] https://api.flutter.dev/flutter/services/TextSelectionDelegate-mixin.html
    [7] https://pub.dev/packages/after_layout
    [8] https://dart.dev/effective-dart/usage#error-handling
    [9] https://pub.dev/packages/json_serializable
    [10] https://pub.dev/packages/freezed
    [11] https://pub.dev/packages/flutter_riverpod
    [12] https://github.com/rrousselGit/riverpod/tree/master/packages/riverpod_graph
    [13] https://pub.dev/packages/mockito
    [14] https://medium.com/flutter-jp/isolate-a3f6eab488b5
    参考
    Attributions:

    https://github.com/logos
    ● https://www.flaticon.com/free-icon/communications_6134704
    ● https://www.flaticon.com/free-icon/svg_337954
    ● https://www.flaticon.com/free-icon/folder_716784
    ● https://www.flaticon.com/free-icon/cloudy_1163661
    ● https://www.flaticon.com/free-icon/smartphone_7344131
    ● https://www.flaticon.com/free-icon/folder_11580838
    ● https://www.flaticon.com/free-icon/database_1602309
    ● https://www.flaticon.com/free-icon/merge_7382043
    ● https://www.flaticon.com/free-icon/documents_3073439
    ● https://www.flaticon.com/free-icon/file_342348
    ● https://www.flaticon.com/free-icon/diagram_2500189
    ● https://www.flaticon.com/free-icon/flexibility_9583638

    View full-size slide