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

I/O Extended 2023 - Flutter 活用事例

I/O Extended 2023 - Flutter 活用事例

Daichi Furiya (Wasabeef)

August 24, 2023
Tweet

More Decks by Daichi Furiya (Wasabeef)

Other Decks in Programming

Transcript

  1. Flutter
    Flutter の活用事例
    (株式会社サイバーエージェント)

    View full-size slide

  2. はじめに
    新規アプリを Flutter で作り始める際に考えたこと
    (株式会社サイバーエージェントの場合)
    Image by jcomp on Freepik

    View full-size slide

  3. ● エディターは?
    ● CI は?
    ● 動作確認・テストは?
    ● どういう技術使いたい?
    ● 開発期間は?
    ● Flutter 経験者は?
    ● どの技術を使えばシステム要件を満
    たすことが出来る?
    要件定義 エンジニアの体制 モチベーション
    ● アーキテクチャどうする?
    ● 参考にできるものはある?
    ● 慣れてる MVVM?Redux?
    Flutter の将来性 開発体験 アーキテクチャ
    はじめに
    01 02 03
    04 05 06
    ● Flutter で大丈夫?

    View full-size slide

  4. システム要件や機能要件などから Flutter
    でも最後まで作り切れるかどうかを判断す
    る。
    要件定義
    要件定義
    Image by freepik on Freepik

    View full-size slide

  5. 5
    対象端末
    ● Android と iOS が対象?
    ● タブレット最適化はある?
    ● テレビデバイスは?
    要件定義
    Image by macrovector on Freepik

    View full-size slide

  6. ● 動画再生(ライブ、オンデマンド)
    ● 課金
    ● PUSH 通知
    ● 検索
    ● OS 毎に違うデザイン
    機能要件は?
    要件定義
    WINTICKET

    View full-size slide

  7. Flutter では難しいことのポイント
    ● ライブラリなどで対応していない機能を
    iOS/Android をそれぞれで作る事になった時
    ● Swift/Kotlin で作るほどのパフォーマンス
    Flutter では難しいこと
    要件定義
    ● ユーザー認証(Firebase など)
    ● 単純な動画再生(ライブ、オンデマンド)
    ● 課金
    ● PUSH 通知
    ● 検索
    ● OS で共通したデザイン
    Flutter でも出来ること
    Flutter だと難しいこと
    ● OS 毎に全く違うデザイン
    ○ Flutter を使うメリットがない
    ● リッチなアニメーションでパフォーマンス
    ● OS 最新機能への追従

    View full-size slide

  8. ここで説明する体制は一つの例にしか過
    ぎませんが、一般的に Swift/Kotlin でアプ
    リ開発する場合にはリリーススケジュール
    を合わせることを考えると同人数程度必要
    になってくると思います。
    エンジニアの体制
    エンジニアの体制
    サーバエンジニア Web エンジニア
    iOS エンジニア Android エンジニア

    View full-size slide

  9. 少ない人数でも開発を始められる
    クロスプラットフォームでアプリが開発でき
    るのであれば結果として少ない人数で
    iOS/Android アプリが作れることとなる。
    または、iOS/Android それぞれ一人だった
    場合には Flutter にすることでコードレ
    ビューを出来て結果として品質向上に繋っ
    たりもします。
    エンジニアの体制
    エンジニアの体制
    サーバエンジニア Web エンジニア
    Flutter エンジニア

    View full-size slide

  10. ここで説明するのは現在携わっているプロ
    ジェクトの状況です。
    Android/iOS 経験者であれば Flutter 開
    発は敷居が低いのかなと思っていますし、
    アプリ開発経験がなくても Web の React
    経験者があれば Flutter の開発も敷居が
    思ったほど高くないと思っています。
    エンジニアの体制
    エンジニアの体制 要件定義 & 技術検証
    アーキテクチャ & 環境構築
    Flutter 経験者 1 人
    Android 経験者 1 人
    Flutter 経験者 1 人
    Android 経験者 2 人
    機能開発
    Flutter 経験者 1 人
    Android 経験者 2 人
    iOS 経験者 1 人
    新卒 1 人

    View full-size slide

  11. モチベーション
    モチベーション
    結局のところ、その技術を使いたいかどう
    かが技術選定にも大きく関わってくると思う
    のでやりたいならやってみましょう。
    Image by jigsawstocker on Freepik

    View full-size slide

  12. 6 つのプラットフォーム (Android、iOS、Web、
    Windows、macOS、Linux) での Flutter サポートによ
    り、100 万以上ものアプリが Flutter を利用していま
    す。
    サイバーエージェントの場合は新規にアプリを作るもの
    関してはほぼ Flutter を選択しています。
    Flutter の将来性
    Flutter の将来性
    Image by iwat1929 on Freepik

    View full-size slide

  13. Flutter の開発体験はすごい良いと思いま
    す。ホットリロードも問題無く動きますし、エ
    ディターのプラグインなども豊富です。
    何より開発中は Flutter Web をフル活用
    することでチーム開発でのやりやすさが向
    上します。
    開発体験
    開発体験
    Image by Gaelle Marcel on Unsplash

    View full-size slide

  14. ● Device Preview
    ● Widgetbook
    Flutter Web を活用する
    開発体験
    Image by flaticon on Freepik

    View full-size slide

  15. Device Preview
    Device Preview は別のデバイスでの表示をシミュ
    レートしてくれるパッケージです。
    例えば Flutter Web (Chrome) 上で iPhone で表
    示した時の UI を出してくれます。
    開発体験
    Flutter Gallery
    https://pub.dev/packages/device_preview

    View full-size slide

  16. Flutter Web (Chrome) で iPhone や
    Samsung 端末をシミュレートしたり端
    末の向きを変えています。
    Flutter Web にさえすれば GitHub で
    コードレビューする際などで動作確認
    が楽になります。
    Device Preview
    開発体験
    https://pub.dev/packages/device_preview

    View full-size slide

  17. 例えば、GitHub の PR に対して IssueOps として
    GitHub Actions で Device Preview をビルド して
    GitHub Pages には PR 毎にデプロイし、コメントにそ
    の URL を貼り付けるようにしています。
    その後、マージされたらこの URL は削除しています。
    GitHub の PR で動作させる
    開発体験

    View full-size slide

  18. また、main branch にマージされた際にも main
    branch の状態を GitHub Pages に デプロイされる
    ようにし、README に URL を貼って確認できるように
    しています。
    ※ GitHub Pages は一般公開しないように Private
    設定にしています。
    GitHub の PR で動作させる
    開発体験

    View full-size slide

  19. Widgetbook
    フロントエンド界隈で利用されている
    Storybook の Flutter 版です。UI コンポーネ
    ントをカタログのように表示することができ、
    Flutter Web にも対応しているので GitHub
    Pages などにデプロイすることで Web 上で
    表示できます。
    ※ Screenshot の取得には対応していない
    ので別でやる必要があります。
    開発体験
    Maestro
    https://www.widgetbook.io/

    View full-size slide

  20. ● Playbook + REG SUIT
    ● Maestro
    Testing
    開発体験
    Image by Kristina Paparo on Unsplash

    View full-size slide

  21. Playbook + REG SUIT
    Playbook は UI コンポーネントのスクリーンショット
    を撮るためのパッケージ。
    REG SUIT は Visual Regression Test (VRT) のため
    のツール。
    Playbook で取得したスクリーンショットを REG SUIT
    で読み込み VRT を行っています。
    https://github.com/playbook-ui/playbook-flutter
    https://github.com/reg-viz/reg-suit
    開発体験

    View full-size slide

  22. Maestro
    E2E テストが出来るフレームワークです。
    YAML でテストケースを書き iOS/Androidエ
    ミュレータで実行することができます。
    YAML で書ける点と YAML 内で JavaScript
    を実行することができるので他のツールに比
    べると柔軟性が高いです。
    開発体験
    Maestro
    https://maestro.mobile.dev/

    View full-size slide

  23. アプリのアーキテクチャを考える上で一番重要に考え
    ていることは状態管理をどう扱うかだと思っています。
    ※ 基本的にアーキテクチャは、世の中的にはもっとい
    いと言われるものがあったとしてもチームとして合意が
    取れている状態であれば正解です。
    アーキテクチャ
    アーキテクチャ
    Image by liravega on Freepik

    View full-size slide

  24. グローバルステート
    基本的にはサーバをグローバルス
    テートとして捉えているのでリクエス
    トデータのキャッシュがほとんど解
    決してくれるはずです。
    ただし、複数のスクリーンなどで使われ
    るような認証トークンなどには
    Riverpod で管理しています。
    ローカルステート
    スクリーン、コンポーネント内で完
    結するデータの管理方法です。よ
    くある例で UI に表示するローティ
    ングの状態だったり、ボタンの有
    効無効の切り替え用だったりする
    ものは Flutter Hooks で管理して
    います。
    アーキテクチャ
    アーキテクチャ
    サーバリクエストとキャッシュ
    GraphQL Flutter を利用しています。
    GraphQL Flutter はレスポンスデータ
    のキャッシュもしてくれます。

    View full-size slide

  25. GraphQL Flutter
    サーバリクエストとキャッシュは GraphQL
    Flutter が担ってもらっています。サーバリクエ
    ストに必要な情報(Firebase Auth の Id
    Token など)以外は基本的にグローバルス
    テートとしては持たないようにしています。
    https://pub.dev/packages/graphql_flutter
    https://pub.dev/packages/graphql_codegen
    開発体験
    サーバリクエストとキャッ
    シュ
    graphql_flutter
    graphql_codegen

    View full-size slide

  26. サーバリクエストと
    キャッシュ
    GraphQL Flutter
    GraphQL の設計については Fragment
    Colocation を基本設計としており、それぞれ
    のコンポーネントで必要なデータは
    Fragment としてそれぞれでコンポーネントと
    同様の場所で .graphql ファイルを定義して
    います。
    Query は画面から呼びます。
    アーキテクチャ
    Maestro
    Home Screen
    User Component Feed Component
    query Home {
    user {
    ...UserParts
    }
    feed {
    ...FeedParts
    }
    }
    # ユーザー情報
    fragment UserParts on User {
    id
    }
    mutation CreateAccount {
    signUp {
    user { id }
    }
    }
    # フィード情報
    fragment FeedParts on Feed {
    id
    title
    body
    }
    https://pub.dev/packages/graphql_flutter
    https://pub.dev/packages/graphql_codegen

    View full-size slide

  27. GraphQL Flutter
    右のディレクトリ構成のようにコンポーネント
    と同階層のところに .graphql の定義を置い
    ています。
    サーバリクエストと
    キャッシュ
    アーキテクチャ
    Maestro
    lib/
    ├── data
    │ └── schema
    │ └── main.graphql
    └── ui
    └── screen
    ├── home
    │ ├── component
    │ │ ├── item
    │ │ │ ├── feed_item.dart
    │ │ │ └── feed_item_fragment.graphql
    │ │ └── text
    │ │ ├── user_name.dart
    │ │ └── user_name_fragment.graphql
    │ ├── home_query.dart
    │ └── home_screen.dart
    ├── mypage
    └── settings
    https://pub.dev/packages/graphql_flutter
    https://pub.dev/packages/graphql_codegen

    View full-size slide

  28. ローカルステート
    アーキテクチャ
    Maestro
    @override
    Widget build(BuildContext context) {
    final isLoading = useState(false);
    ./ ... 何か
    return Scaffold(
    body: Stack(
    children: [
    const Text('Body'),
    if (isLoading.value)
    const Center(
    child: CircularProgressIndicator(),
    ),
    ],
    ),
    );
    }
    https://maestro.mobile.dev/
    Flutter Hooks の useState(...)
    Flutter Hooks
    例のようにこのウィジェット内で完結するよう
    なデータの場合は Flutter Hooks の
    useState(...) を利用して管理しています。
    それ以外にも useContext や useCallback
    などもよく利用しています。

    View full-size slide

  29. グローバルステート
    Riverpod
    基本的にはサーバをグローバルステート
    として捉えているのでリクエストデータの
    キャッシュがほとんど解決してくれるはず
    です。
    ただし、複数のスクリーンなどで使われるよう
    な認証トークンなどには Riverpod で管理し
    ています。
    アーキテクチャ
    Maestro
    https://maestro.mobile.dev/
    part 'id_token_state.g.dart';
    typedef IdToken = String;
    @Riverpod(keepAlive: true, dependencies: [firebaseAuth])
    class IdTokenState extends _$IdTokenState {
    @override
    IdToken build() { return ''; } ./ 初期値は空
    Future fetch() async {
    final user = ref.watch(firebaseAuthProvider).currentUser;
    if (user .= null) return;
    update(await user.getIdToken());
    }
    void update(String token) {
    if (state .= token) state = token;
    }
    }
    Riverpod の Notifier
    @override
    Widget build(BuildContext context) {
    final idToken = ref.watch(idTokenStateProvider);
    ./ ... 何か
    }

    View full-size slide

  30. 全体のディレクトリ構造はこういう形で定義し
    ています。作るにつれて苦しくなっていく箇所
    は随時考え直しています。
    ディレクトリ構成
    アーキテクチャ
    Maestro
    lib/
    ├── data/
    │ ├── network/
    │ └── system/
    ├── foundation/
    │ ├── extension/
    │ └── firebase/
    │ ├── auth/ # Firebase Auth 関連
    │ └── messaging/ # Firebase Messaging 関連
    ├── state/ # グローバルの状態管理関連
    ├── ui/ # UI 関連のモジュール(GraphQL は各画面で定義)
    │ ├── screen/
    │ │ └── home/
    │ │ ├── home_screen.dart # 画面
    │ │ ├── home_screen_e2e.yaml # Maestro のファイル
    │ │ ├── home_screen_vrt.dart # Visual Regression Test のファイル
    │ │ ├── hook/ # 画面固有の Hooks
    │ │ │ └── use_update_home.dart
    │ │ ├── component/ # 画面固有のコンポーネント
    │ │ │ ├── home_navigation.dart
    │ │ │ ├── home_card.dart
    │ │ │ └── home_card_fragment.dart # コンポーネントの GraphQL ファイル
    │ │ └── top
    │ │ ├── home_top_screen_e2e.yaml
    │ │ └── home_top_screen.dart
    │ ├── theme/ # グローバルテーマ設定
    │ │ ├── app_theme.dart
    │ │ └── app_text.dart
    │ ├── hook/ # 汎用的な Hooks
    │ │ ├── use_debounce.dart
    │ │ ├── use_debounce_test.dart
    │ │ ├── use_sign_in.dart
    │ │ ├── use_sign_in_test.dart
    │ │ ├── use_sign_out.dart
    │ │ └── use_sign_out_test.dart
    │ └── component/ # 汎用 UI コンポーネント
    │ ├── fab/
    │ └── text/
    └── use_case/

    View full-size slide

  31. Thank You
    Daichi Furiya / wasabeef
    Google Developers Expert/CyberAgent, Inc

    View full-size slide