📌

Flutterエンジニアのためのアーキテクチャ入門 - 公式(日本語)

2024/11/21に公開

2024/11/18に、Flutterの公式ドキュメントより、Flutterの設計についての記載が公開されたので、まとめてみます。
直訳やLLMの翻訳では分かりづらい箇所をなるべく理解しやすいように噛み砕いて日本語訳しました。

https://docs.flutter.dev/app-architecture

初めに

以下の内容が学べます

  • アーキテクチャの利点
  • 一般的なアーキテクチャ原則
  • Flutterチームが推奨するアーキテクチャ
  • MVVMと状態管理
  • 依存性注入
  • 堅牢なFlutterアプリケーションを作成するための一般的なデザインパターン

アーキテクチャの利点

優れたアーキテクチャには、エンジニアとユーザーにとってに多くのメリットがあります。

  • 保守性 - 時間の経過とともに変更、更新、問題の修正が容易になります。
  • スケーラビリティ - コードのコンフリクトを最小限に抑え、より多くの人が同時に同じコードを変更することを可能にします。
  • テスト可能性 - 入力と出力が明確に定義されたシンプルなクラス定義により、モックやテストが容易になります。
  • 認知負荷の軽減 - プロジェクトに不慣れなエンジニアでも、短時間でコードを理解でき、生産性が向上し、コードレビューにかかる時間も短縮されます。
  • 優れたUX - 各機能をより早く、より少ないバグでリリースできます。

このガイドの使い方

このガイドは、スケーラブルなFlutterアプリケーションを構築するためのガイドであり、機能豊富なアプリケーションを構築している、複数のエンジニアのチームで同じコードを開発している、などのケースに当てはまる方向けに書かれています。成長を続けるチームとコードを持つFlutterアプリを作成している場合は、このガイドが役立ちます。

このガイドでは、一般的なアーキテクチャに関するアドバイスに加えて、ベストプラクティスの具体的な例を示し、具体的な推奨事項も取り上げています。一部のライブラリは交換可能であり、独自の複雑さを持つ非常に大規模なチームでは、一部の部分が当てはまらない場合があります。いずれの場合も、アイデアは健全です。このガイドは、Flutterアプリを構築するのに推奨される方法となっています。

このガイドの第一部では、一般的なアーキテクチャの原則について概要から学習します。第二部では、Flutterアプリを設計するための具体的な推奨事項について説明します。第三部、最後には、設計パターンのリストと、推奨事項を実際に示すサンプルコードがあります。

1. 共通のアーキテクチャ概念(Common architecture concepts)

このセクションでは、アプリ開発において、アーキテクチャを決める際に参考となる原則や、それがFlutterでどのように適用されているかを紹介します。推奨されるアーキテクチャやベストプラクティスに関連する用語や概念について、詳しく探求するための入門として役立ててください。

関心の分離 (Separation of concerns)

「関心の分離」はアプリ開発の基本原則であり、アプリケーションの機能を独立した自己完結型の単位に分割することで、モジュール化と保守性を促進します。(他の機能に依存せずに、その機能単体で成り立つ関数などをイメージしてください)

これは、UIロジック(UIに関連する振る舞いや制御をするための処理)とビジネスロジック(データのやり取りや、ログイン処理など、UIに関連しない処理)を分離することを指します。

各レイヤー内では、機能や特徴ごとにアプリケーションをさらに分離する必要があります。

: 認証ロジックは検索ロジックとは別のクラスに分けるべきです。

Flutterでの適用例:
UIレイヤーのWidgetにおいても、可能な限りロジックを最小限にした、再利用可能でシンプルなWidgetを作成します。

レイヤードアーキテクチャ (Layered architecture)

Flutterアプリケーションはレイヤーに分けて記述するべきです。

レイヤードアーキテクチャは、アプリケーションを特定の役割と責務を持つ明確なレイヤーに整理する設計パターンです。
通常、アプリケーションはその複雑さに応じて 2~3レイヤー(層) に分けられます。

  1. UI層:
    • ビジネスロジック層が提供するデータをユーザーに表示し、ユーザーの操作を処理します。
    • プレゼンテーション層とも呼ばれます。
  2. ロジック層:
    • コアビジネスロジックを実装し、データ層とUI層間のやり取りを仲介します。
    • ドメイン層とも呼ばれます。
    • この層は クライアント側で複雑なビジネスロジックが必要な場合のみ実装 します。
    • ユーザーにデータを提供し、ユーザーがそのデータを変更できるようにすることにのみ関心があります (俗にCRUDアプリと呼ばれます)。これらのアプリでは、この層は必要ない場合もあります。
  3. データ層:
    • データベースやプラットフォームのプラグインなどのデータソースとのやり取りを管理し、ビジネスロジック層にデータとメソッドを提供します。

各層は直接上下の層としか通信できません。
UI層がデータ層を直接知ることはなく、その逆も同様です。

単一の真実の情報源 (Single source of truth)

アプリ内のすべてのデータ型には、信頼できる唯一の情報源(SSOT)を持たせるべきです。

  • SSOTは、ローカルまたはリモートの状態を表現します。
  • SSOT Classは、アプリ内でデータを変更できる場合、唯一その変更を行う権限を持ちます。

これにより、アプリケーションのバグが大幅に減少し、同じデータのコピーが1つだけになるため、コードが単純化されます。

通常、SSOTは、「Repository(リポジトリ)」 と呼ばれるデータ層内のClass に保持されます。アプリ内の各データ型に対して1つのRepository Class を用意するのが一般的です。

:
この原則は、アプリケーションのレイヤーやコンポーネント間だけでなく、個々のClass内にも適用できます。
例えば、Dart Classでは、SSOTフィールドから値を派生するためにGettersを使用することができます(複数のフィールドを独立して更新する必要がある代わりに)。
また、関連する値をグループ化するためにRecordsのリストを使用することもできます(インデックスが同期されなくなる可能性がある並列リストの代わりに)。

単方向データフロー(Unidirectional data flow, UDF)

単方向データフロー(UDF)は、stateとそのstateを表示するUIを切り離す設計パターンを指します。

簡単に言うと、stateはデータ層からロジック層を通じて、最終的にUI層のWidgetに流れます。

ユーザーの操作から発生するイベントは逆方向に流れ、プレゼンテーション層からロジック層を経てデータ層に到達します。

UDFにおける更新ループの流れ:

  1. UI層
    ボタンのクリックなど、ユーザー操作によってイベントが発生します。Widgetのイベントハンドラーコールバック(クリックイベントなどの処理が呼ばれた後に、呼ばれる処理)がロジック層のClassのメソッドを呼び出します。
  2. ロジック層
    ロジックClassが、データを変更するための特定のRepositoryのメソッドを呼び出します。
  3. データ層
    Repositoryが(必要に応じて)データを更新し、その新しいデータをロジックClassに提供します。
  4. ロジック層
    ロジックClassが新しいstateを保存し、それをUIに送信します。
  5. UI層
    UIがViewModelの新しいstateを表示します。

新しいデータがデータ層から始まる場合もあります。
たとえば、リポジトリがHTTPサーバーをポーリングして新しいデータを取得する場合です。この場合、データフローは後半部分のみを経由します。
最も重要な考え方は、データの変更は常にSSOT(データ層)で行われるということです。これにより、コードが理解しやすくなり、エラーが発生しにくくなり、不正または予期しないデータの生成が防止されます。

UIはイミュータブルなstateの関数である(UI is a function of (immutable) state)

Flutterは宣言的であるため、アプリの現在のstateを反映する形でUIを構築します。
stateが変化すると、そのstateに依存するUIの再構築が行われます。Flutterではこれを「UIはstateの関数である(UI is a function of state)」と表現します。

データは不変で永続的であるべきで、Viewには可能な限り少ないロジックしか含まれないようにすることが重要です。
これにより、アプリを閉じた際にデータが失われる可能性を最小限に抑え、アプリをよりテストしやすく、バグに強いものにします。

拡張性(Extensibility)

拡張性が高い設計では、新しい機能を追加したり、既存のロジックを変更したりする際に、他の部分に影響を与えるリスクが少なくなります。

各アーキテクチャのComponentは、明確に定義された入力と出力のリストを持つべきです。
例えば、ロジック層のViewModelは、Repositoryのようなデータソースを入力として受け取り、コマンドやView用にフォーマットされたデータを出力として公開する必要があります。

このようにクリーンなインターフェースを使用することで、クラスの具体的な実装を差し替える際に、そのインターフェースを利用するコードを変更する必要がなくなります

テスト容易性(Testability)

ソフトウェアを拡張可能にする原則は、ソフトウェアのテスト容易性も向上させます。
例えば、Repositoryをモックすることで、ViewModelの自己完結型ロジックをテストできます。このViewModelのテストでは、アプリケーションの他の部分をモックする必要がありません。
また、Widget自体とは別にUIロジックをテストすることができます。

アプリの柔軟性も向上します。新しいロジックや新しいUIを追加することが簡単でリスクが低くなります。
例えば、新しいViewModelを追加しても、データやビジネスロジック層の他のロジックに影響を与えることはありません

2. アプリのアーキテクチャのガイド(Guide to app architecture)

このガイドでは、ベストプラクティスを用いてアプリを構築する方法を解説します。この推奨事項は、ほとんどのアプリに適用でき、スケール、テスト、保守がしやすくなります。ただし、これらはあくまでガイドラインであり、必ずしも厳守する必要はありません。アプリの特定の要件に応じて適応させてください。

このセクションでは、Flutter アプリケーションを設計する方法についての概要を説明します。
アプリケーションのレイヤーと、各レイヤー内に存在するクラスについて解説します。この後のセクションでは、具体的なコード例を示し、この推奨事項を実装したFlutterアプリケーションの作り方を説明します。

プロジェクト構造の概要(Overview of project structure)

Flutterアプリを設計する際、最も重要な原則は「関心の分離(Separation of concerns)」です。Flutterアプリケーションは、UI層データ層の2つのレイヤーに分割するべきです。

各レイヤーはさらに異なるコンポーネントに分割されます。各コンポーネントは、明確な責任、定義されたインターフェース、境界、依存関係を持っています。このガイドでは、アプリケーションを以下のようなコンポーネントに分割することを推奨します:

  • Views
  • ViewModels
  • Repositories
  • Services

MVVM

もし「Model-View-ViewModel(MVVM)」デザインパターンに出会ったことがあるなら、これは馴染みのあるものかもしれません。MVVMは、アプリケーションの機能を3つの部分に分けるデザインパターンです: Model、ViewModel、そしてViewです。
ViewとViewModelはアプリケーションのUIレイヤーを構成します。RepositoriesやServicesは、アプリケーションのデータ、つまりMVVMのModelレイヤーを表します。これらの各コンポーネントについては次のセクションで定義されています。

アプリケーション内の各機能は、UIを記述する1つのViewと、ロジックを処理する1つのViewModel、アプリケーションデータの信頼できる情報源としての1つ以上のRepository、そしてクライアントサーバーやプラットフォームプラグインのような外部APIとやりとりする0個以上のServiceを含みます。

アプリケーションの単一機能が必要とする可能性があるオブジェクトは以下の通りです。

これらの各オブジェクトとそれらを接続する矢印については、このページの最後までに詳しく説明されます。このガイド全体を通して、以下の簡略化されたバージョンの図がアンカーとして使用されます。


Note

複雑なロジックを持つアプリでは、UI層とデータ層の間にロジック層が存在する場合もあります。このロジック層は一般的に「ドメイン層」と呼ばれます。ドメイン層には、InteractorsまたはUse-Casesと呼ばれる追加のコンポーネントが含まれています。ドメイン層については、このガイドの後半で説明します。


アプリケーションの機能はユーザー中心であり、UI層によって定義されます。ViewとViewModelのペアの各インスタンスが、アプリ内の1つの機能を定義します。これはしばしばアプリ内の1つの画面を指しますが、必ずしもそうである必要はありません。

例えば、ログインとログアウトを考えてみましょう。ログインは一般的に、ログインする方法をユーザーに提供することだけを目的とした特定の画面で行われます。アプリケーションのコードでは、ログイン画面は LoginViewModel クラスと LoginView クラスで構成されます。

一方で、アプリからのログアウトは通常、専用の画面で行われることはありません。ログアウト機能は通常、メニューやユーザーアカウント画面、またはその他さまざまな場所に配置されたボタンとしてユーザーに提示されます。そして、それが複数の場所で提供されることもよくあります。この場合、LogoutViewModel と、他のWidgetに簡単に組み込める単一のボタンだけを含む LogoutView を持つことになるかもしれません。

Views

Flutterにおいて、ViewsはアプリケーションのWidget Classです。ViewsはUIをレンダリングする主な手段であり、ビジネスロジックを含むべきではありません。レンダリングに必要なすべてのデータはViewModelから渡されるべきです。

Viewが含むべき唯一のロジック:

  • ViewModel内のフラグやnullableフィールドに基づいてWidgetを表示または非表示にする単純なif文
  • アニメーションロジック
  • デバイス情報(画面サイズや向きなど)に基づいたレイアウトロジック
  • シンプルなルーティングロジック

データに関連するすべてのロジックは、ViewModelで処理されるべきです。

ViewModels

ViewModelは、Viewをレンダリングするために必要なアプリケーションデータを公開します。このページで説明されているアーキテクチャデザインでは、Flutterアプリケーションのロジックの大部分はViewModelに存在します

ViewModelの主な責任:

  1. リポジトリからアプリケーションデータを取得し、Viewでの表示に適した形式に変換すること。
    例えば、データのフィルタリング、ソート、集計などを行います。

  2. Viewで必要とされる現在の状態を維持すること。
    これにより、Viewはデータを失うことなく再構築できます。

    • 例: View内のWidgetを条件付きでレンダリングするためのbooleanフラグを含む。
    • 例: カルーセル内で画面上にアクティブなセクションを追跡するフィールドを保持する。
  3. Viewにコールバック(コマンドと呼ばれる)を公開すること。
    これらのコールバックはイベントハンドラ(ボタンの押下やフォームの送信など)にアタッチできます。

コマンドについて:
Command パターン に基づいて名付けられたもので、Dartの関数として実装されます。

  • Viewがその実装を知らずに複雑なロジックを実行できるようにします。
  • コマンドは、ViewModelクラスのメンバーとして記述され、Viewクラス内のジェスチャーハンドラから呼び出されます。

FlutterにおけるMVVMの基本的な紹介については、FlutterのGetting Startedガイドの「State Management」ページを参照してください。

データ層

アプリのデータ層は、ビジネスデータとロジックを処理します。データ層は2つのアーキテクチャコンポーネントで構成されています: ServicesRepositories。これらのコンポーネントは、再利用性とテストの容易性を高めるために、明確に定義された入力と出力を持つべきです。

MVVMの言葉を用いると、ServicesとRepositoriesはModel層を構成します。

Repositories

Repositoryクラスは、モデルデータの信頼できる唯一の情報源(SSOT)です。サービスからデータを取得し、その生データをドメインモデルに変換する役割を担います。ドメインモデルはアプリケーションが必要とするデータを表し、ViewModelクラスが利用できる形式に整形されます。アプリ内で扱うデータの種類ごとに対応するRepositoryクラスがあるべきです。

Repositoriesの役割
Repositoriesは、サービスに関連するビジネスロジックを処理します。
例えば:

  • キャッシング
  • エラーハンドリング
  • リトライロジック
  • データのリフレッシュ
  • 新しいデータを取得するためのサービスへのポーリング
  • ユーザーアクションに基づくデータの更新

Repositoriesは、アプリケーションデータをドメインモデルとして出力します。例えば、ソーシャルメディアアプリでは、UserProfileRepository クラスが Stream<UserProfile?> を公開し、ユーザーがサインインまたはサインアウトするたびに新しい値を発行することがあります。

  • リポジトリによって出力されたドメインモデルは、ViewModelsによって消費されます。
  • RepositoriesとViewModelsは多対多の関係を持ちます。
    • ViewModelは必要なデータを取得するために複数のRepositoryを使用できます。
    • 1つのRepositoryは複数のViewModelで利用されることがあります。

Repositoriesは互いを認識してはなりません。アプリケーション内で、2つのRepositoryからデータを必要とするビジネスロジックがある場合、特に、RepositoryとViewModel間の関係が複雑な場合、そのデータを統合する処理はViewModelまたはドメイン層で行うべきです。

Services

Servicesはアプリケーションの最下層に位置します。これらはAPIエンドポイントをラップし、非同期レスポンスオブジェクト(FutureStreamオブジェクトなど)を公開します。Servicesはデータロードを分離するためだけに使用され、状態を保持しません。アプリケーションには、データソースごとに1つのServiceクラスを持つべきです。

Servicesがラップする可能性のあるエンドポイントは次のとおりです。

  • iOSやAndroidのAPIのような基盤となるプラットフォーム
  • RESTエンドポイント
  • ローカルファイル

一般的に、必要なデータがアプリケーションのDartコード外に存在する場合、Servicesは特に役立ちます。上記の例はすべて、この条件に該当します。

  • ServicesとRepositoriesは多対多の関係を持ちます。
    • 1つのRepositoryは複数のServiceを使用できます。
    • 1つのServiceは複数のRepositoryで利用されることがあります。

ドメイン層(オプション)

アプリが成長し、機能が増えるにつれて、ViewModelに過剰な複雑さを追加するロジックを抽象化する必要が出てくる場合があります。これらのクラスはしばしばInteractorsUse-Casesと呼ばれます。

Use-Casesは、UI層とデータ層の間の相互作用をシンプルかつ再利用可能にする役割を担います。リポジトリからデータを取得し、それをUI層に適した形式に変換します。

Use-Casesが主に使用される条件:

  1. 複数のリポジトリからデータを統合する必要がある場合
  2. ロジックが非常に複雑な場合
  3. そのロジックが複数のViewModelで再利用される場合

この層がオプションである理由:
すべてのアプリケーションやアプリケーション内のすべての機能が、この層を必要とするわけではありません。もしこの追加層がアプリにメリットをもたらすと考えられる場合は、以下のメリットとデメリットを考慮してください。

Pros Cons
✅ ViewModel内のコードの重複を回避できる ❌ アーキテクチャの複雑さが増し、クラス数が増えるため認知負荷が高くなる
✅ 複雑なビジネスロジックをUIロジックから分離することでテストがしやすくなる ❌ テストには追加のモックが必要になる
✅ ViewModelのコードの可読性を向上できる ❌ コードに追加のボイラープレートが増える

ユースケースによるデータアクセス

Domain層を追加する際のもう一つの考慮事項は、ViewModelがリポジトリデータに直接アクセスし続けるのか、それともViewModelがデータを取得する際にUse-Caseを経由するようにするのかという点です。言い換えれば、ViewModel内で繰り返しのロジックに気づいたときに必要に応じてUse-Caseを追加するのか、それともロジックが単純であってもViewModelがデータを必要とするたびに必ずUse-Caseを作成するのか、という選択です。

後者のアプローチを選んだ場合、前述のメリットとデメリットがさらに顕著になります。アプリケーションコードは非常にモジュール化され、テストがしやすくなりますが、不必要なオーバーヘッドが大幅に増えるという欠点も生じます。

良いアプローチは、必要な場合にのみUse-Caseを追加することです。もしViewModelがほとんどの場合でUse-Caseを介してデータにアクセスするようになった場合、その時点でコードをリファクタリングしてUse-Caseを全面的に活用する設計に変更することもできます。このガイドで後ほど使用する例では、いくつかの機能にUse-Caseを使用していますが、ViewModelが直接リポジトリとやりとりするケースも含まれています。複雑な機能は最終的に次のような構造になる可能性があります:

この方法でUse-Caseを追加する場合、以下のルールに基づきます:

  • Use-Caseはリポジトリに依存します
  • Use-Caseとリポジトリは多対多の関係を持ちます
  • ViewModelは1つ以上のUse-Caseと1つ以上のリポジトリに依存します

この方法では、設計は「層が重なるラザニア」のようではなく、UI層とデータ層という2つのメインにDomain層というサイドを持つ「盛り付けられたディナー」のような構造になります。Use-Caseは、明確に定義された入力と出力を持つ単純なユーティリティクラスとして機能します。このアプローチは柔軟で拡張可能ですが、秩序を維持するためには細心の注意が必要です。

3. アーキテクチャの推奨事項とリソース(Architecture recommendations and resources)

このガイドでは、アーキテクチャのベストプラクティス、その重要性、そしてそれをFlutterアプリケーションに採用するかどうかの推奨について説明します。これらの推奨事項はあくまで「推奨」であり、絶対的なルールではありません。アプリケーションの独自の要件に合わせて適応させるべきです。

このページに記載されているベストプラクティスには優先度が設定されており、Flutterチームがどれだけ強く推奨するかを反映しています。

  • Strongly recommend(強く推奨):
    新しいアプリケーションを構築し始める場合、この推奨事項を常に実装すべきです。既存のアプリでも、現在のアプローチと根本的に衝突しない限り、このプラクティスを実装するためのリファクタリングを強く検討すべきです。

  • Recommend(推奨):
    このプラクティスは、アプリの改善に寄与する可能性が高いものです。

  • Conditional(条件付き):
    このプラクティスは、特定の状況下でアプリの改善に寄与する場合があります。

責務の分離 (Separation of Concerns)

アプリをUI層データ層に分け、それぞれの層内でロジックを責務ごとにクラスに分割すべきです。

推奨事項 説明
明確に定義されたデータ層とUI層を使用する。 (強く推奨) 責務の分離は最も重要なアーキテクチャ原則です。データ層はアプリケーションデータを公開し、ビジネスロジックを含みます。UI層はデータを表示し、ユーザーイベントを受け取ります。それぞれロジックとWidgetを分離して管理すべきです。
データ層にリポジトリパターンを使用する。 (強く推奨) リポジトリパターンはデータアクセスロジックをアプリケーションの他の部分から分離する設計手法です。リポジトリクラスとサービスクラスを作成し、データ層における抽象化を実現します。
UI層でViewModelsとViewsを使用する。 (強く推奨) ViewとViewModelを分離することでWidgetを「愚直な」状態に保ち、エラーを防ぎます(MVVMパターン)。
ChangeNotifiersとListenablesを使用してWidgetを更新する。 (条件付き) ChangeNotifierは、WidgetがViewModelの変更を監視する便利な方法です。状態管理の選択肢は多く、個人の好みによる部分もあります。
Widgetにロジックを含めない。 (強く推奨) ビジネスロジックはViewModelのメソッドにカプセル化するべきです。Viewに含むべきロジックはシンプルな条件分岐やアニメーション、レイアウト、簡単なルーティングロジックのみです。
ドメイン層を使用する。 (条件付き) アプリが非常に複雑なロジックを含む場合、またはViewModelでロジックが重複する場合にドメイン層を追加します。大規模なアプリでは有用ですが、ほとんどのアプリでは不要なオーバーヘッドをもたらすこともあります。

データの取り扱い (Handling Data)

データを慎重に取り扱うことで、コードが理解しやすくなり、エラーが減り、不正なデータ生成を防げます。

推奨事項 説明
単方向データフローを使用する。 (強く推奨) データの更新はデータ層からUI層にのみ流れるべきです。UI層での操作はデータ層で処理されます。
ユーザー操作のイベント処理にコマンドを使用する。 (推奨) コマンドはレンダリングエラーを防ぎ、UI層からデータ層へのイベント送信を標準化します。
不変データモデルを使用する。 (強く推奨) 不変のデータモデルは、データがモデル内でのみ更新されることを保証するために重要です。
freezedやbuilt_valueを使用して不変データモデルを生成する。 (推奨) freezedbuilt_valueを使用すると、JSONシリアライズ/デシリアライズ、ディープイコールチェック、コピー機能などの便利なメソッドを生成できます。ただし、モデル数が多い場合ビルド時間が増加します。
APIモデルとドメインモデルを分ける。 (条件付き) モデルを分離することでViewModelやUse-Caseの複雑さを防ぎますが、冗長さが増します。大規模なアプリで有用です。

アプリ構造 (App Structure)

良いコード構造はアプリの健全性とチームの作業効率を向上させます。

推奨事項 説明
依存性注入を使用する。 (強く推奨) 依存性注入により、グローバルにアクセス可能なオブジェクトを排除し、エラーを減らします。providerパッケージを推奨します。
go_routerをナビゲーションに使用する。 (推奨) go_routerは90%のFlutterアプリで推奨されるナビゲーション方法です。特定のユースケースではNavigator APIや他のパッケージを検討してください。
クラス、ファイル、ディレクトリに標準的な命名規則を使用する。 (推奨) クラス名にはアーキテクチャコンポーネントを表す名前を使用してください(例: HomeViewModelUserRepository)。混乱を避けるため、Flutter SDKオブジェクトと紛らわしい名前は避けます。
抽象リポジトリクラスを使用する。 (強く推奨) リポジトリはアプリのデータの信頼できる情報源として機能します。抽象リポジトリクラスを作成することで、開発用やステージング用など異なる実装を柔軟に切り替えられます。

テスト (Testing)

テストはアプリの柔軟性を高め、新しいロジックやUIを追加する際のリスクを低減します。

推奨事項 説明
アーキテクチャコンポーネントを個別および統合でテストする。 (強く推奨) 各サービス、リポジトリ、ViewModelクラスのユニットテストを作成し、それぞれのメソッドのロジックを個別にテストします。ViewのWidgetテストも実施し、特にルーティングや依存性注入を確認します。
テスト用フェイクを作成し、それを活用できるコードを書く。 (強く推奨) テスト用のフェイクを作成し、入力と出力を重視したコードを書くことで、モジュール化された軽量な関数とクラスが自然と生まれます。

リソース

  • コードとテンプレート

    • Compass app source code
      多くの推奨事項を実装した、フル機能で堅牢なFlutterアプリケーションのソースコード。

    • Flutter skeleton
      多くの推奨事項を含むFlutterアプリケーションテンプレート。

    • very_good_cli
      Flutterの専門家であるVery Good Venturesによって作成されたFlutterアプリケーションテンプレート。このテンプレートは類似したアプリ構造を生成します。

  • ドキュメント

  • ツール

    • Flutter developer tools
      DartとFlutter用のパフォーマンスおよびデバッグツールスイートであるDevTools。

    • flutter_lints
      Flutterチームが推奨するFlutterアプリ向けのリントを含むパッケージ。チーム全体での良いコーディングプラクティスを推奨するために使用します。

Discussion