Android アーキテクチャに関する推奨事項

このページでは、アーキテクチャのベスト プラクティスと推奨事項をいくつか紹介します。これらを採用することで、アプリの品質、堅牢性、スケーラビリティを向上できます。また、アプリのメンテナンスとテストも容易になります。

以下のベスト プラクティスは、トピック別にグループ化されています。それぞれに、推奨事項の強さを示す優先度があります。優先度のリストは次のとおりです。

  • 強く推奨: 自身の手法と根本的に対立する場合を除き、このプラクティスを実装します。
  • 推奨: このプラクティスで、アプリを改善できる可能性があります。
  • 省略可: このプラクティスで、特定の状況下でアプリを改善できる可能性があります。

階層型アーキテクチャ

Google が推奨する階層型アーキテクチャでは、関心の分離を重視しています。データモデルから UI を動作させ、信頼できる唯一の情報源の原則を遵守し、単方向データフローの原則に従います。階層型アーキテクチャに関するベスト プラクティスは次のとおりです。

推奨事項 説明
明確に定義されたデータレイヤを使用します データレイヤは、アプリデータをアプリの他の部分に公開し、アプリのビジネス ロジックの大部分を含みます。
  • リポジトリは、データソースが 1 つだけの場合でも作成します。
  • 小規模なアプリでは、data パッケージやモジュール内にデータレイヤ タイプを配置できます。
明確に定義された UI レイヤを使用します。 UI レイヤは、アプリデータを画面に表示するもので、ユーザー インタラクションの主要なポイントとして機能します。Jetpack Compose は、アプリの UI をビルドする際に推奨される最新のツールキットです。
  • 小規模なアプリでは、ui パッケージやモジュール内にデータレイヤ タイプを配置できます。
UI レイヤのベスト プラクティスについて詳しくは、UI レイヤをご覧ください。
リポジトリを使用して、データレイヤからアプリケーション データを公開します。

コンポーザブルや ViewModel などの UI レイヤのコンポーネントを、データソースと直接やり取りさせないようにします。データソースの例:

  • データベース、DataStore、SharedPreferences、Firebase API
  • GPS 位置情報プロバイダ。
  • Bluetooth データ プロバイダ。
  • ネットワーク接続ステータス プロバイダ。
コルーチンと Flow を使用します。 コルーチンとフローを使用してレイヤ間の通信を行います。

コルーチンのベスト プラクティスについて詳しくは、Android でのコルーチンに関するベスト プラクティスをご覧ください。

ドメインレイヤを使用します。 複数の ViewModel 間でデータレイヤとやり取りするビジネス ロジックを再利用する必要がある場合、または特定の ViewModel のビジネス ロジックの複雑さを簡素化したい場合に、ユースケースを含むドメインレイヤを使用します。

UI レイヤ

UI レイヤの役割は、アプリデータを画面に表示することであり、ユーザー インタラクションの主要なポイントとして機能することです。UI レイヤのベスト プラクティスは次のとおりです。

推奨事項 説明
単方向データフロー(UDF)に従います。 単方向データフロー(UDF)の原則に従い、ViewModel はオブザーバー パターンを使用して UI の状態を公開し、メソッド呼び出しを介して UI からアクションを受け取ります。
メリットをアプリに適用できる場合は、AAC ViewModel を使用します。 AAC ViewModel を使用してビジネス ロジックを処理し、アプリデータを取得して UI の状態を UI に公開します。

ViewModel のベスト プラクティスについて詳しくは、アーキテクチャの推奨事項をご覧ください。

ViewModel のメリットについて詳しくは、ビジネス ロジック状態ホルダーとしての ViewModel をご覧ください。

ライフサイクル対応 UI 状態コレクションを使用します。 適切なライフサイクル対応コルーチン ビルダー collectAsStateWithLifecycle を使用して、UI から UI の状態を収集します。

詳しくは、 collectAsStateWithLifecycle をご覧ください。

ViewModel から UI にイベントを送信しないようにします。 ViewModel でイベントをすぐに処理し、イベント処理の結果で状態を更新します。UI イベントの詳細については、ViewModel イベントを処理するをご覧ください。
単一アクティビティのアプリケーションを使用します。 アプリに複数の画面がある場合、Navigation 3 を使用して画面間を移動し、アプリへのディープリンクを設定します。
Jetpack Compose を使用します。 Jetpack Compose を使用して、スマートフォン、タブレット、折りたたみ式デバイス、Wear OS 向けの新しいアプリを作成します。

次のスニペットは、ライフサイクル対応の方法で UI の状態を収集する方法を示しています。

  @Composable
  fun MyScreen(
      viewModel: MyViewModel = viewModel()
  ) {
      val uiState by viewModel.uiState.collectAsStateWithLifecycle()
  }

ViewModel

ViewModel は、UI の状態を提供し、データレイヤにアクセスする役割を担います。ViewModel に関するベスト プラクティスは次のとおりです。

推奨事項 説明
ViewModel が Android のライフサイクルに依存しないようにします。 ViewModel で、ライフサイクルに関連する型への参照を保持しないようにします。ActivityContextResources を依存関係として渡さないようにします。ViewModel で Context を必要とする場合は、それが適切なレイヤにあるかどうかを慎重に評価してください。
コルーチンと Flow を使用します。

ViewModel は、以下を使用してデータレイヤまたはドメインレイヤとやり取りします。

  • アプリケーション データを受信するための Kotlin Flow
  • viewModelScope を使用してアクションを実行するための suspend 関数
画面レベルで ViewModel を使用します。

再利用可能な UI で ViewModel を使用しないようにします。ViewModel は、以下で使用します。

  • 画面レベルのコンポーザブル。
  • View のアクティビティ / フラグメント。
  • Jetpack Navigation を使用する場合のデスティネーションまたはグラフ。
再利用可能な UI コンポーネントでは、プレーンな状態ホルダークラスを使用します。 再利用可能な UI コンポーネントの複雑さに対処するには、プレーンな状態ホルダークラスを使用します。これにより、状態を外部でホイスティングして制御できるようになります。
AndroidViewModel を使用しないようにします。 AndroidViewModel ではなく ViewModel クラスを使用します。ViewModel で Application クラスを使用しないでください。代わりに、依存関係を UI またはデータレイヤに移行します。
UI 状態を公開します。 ViewModel が、uiState という単一のプロパティを介して UI にデータを公開するようにします。UI に互いに関係のない複数のデータが表示されている場合、VM が複数の UI 状態プロパティを公開する可能性があります。
  • uiStateStateFlow にします。
  • データが階層の他のレイヤからのデータ ストリームとして来る場合は、stateIn 演算子と WhileSubscribed(5000) ポリシーを使用して uiState を作成します。(こちらのコード例をご覧ください)。
  • データレイヤから来るデータ ストリームがない単純なケースでは、不変の StateFlow として公開される MutableStateFlow を使用できます。
  • ${Screen}UiState をデータクラスとして指定できます。データクラスには、データ、エラー、読み込みシグナルを含めることができます。異なる状態が排他的である場合、このクラスがシールクラスになることもあります。

次のスニペットは、ViewModel から UI の状態を公開する方法を示しています。

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

ライフサイクル

アクティビティのライフサイクルを扱う際のベスト プラクティスは次のとおりです。

推奨事項 説明
Activity ライフサイクル コールバックをオーバーライドする代わりに、コンポーザブルでライフサイクル対応エフェクトを使用します。

UI 関連のタスクを実行するために、onResume などの Activity ライフサイクル メソッドをオーバーライドしないでください。代わりに、Compose の LifecycleEffects またはライフサイクル対応コルーチン スコープ(
)を使用します。

  • アクティビティの開始時と停止時に同期処理を行うには、LifecycleStartEffect を使用します。
  • アクティビティが再開または一時停止したときに同期作業を行うには、LifecycleResumeEffect を使用します。
  • repeatOnLifecycle を使用して、ライフサイクル イベントに応じて非同期の作業を行います。
  • collectAsStateWithLifecycle を使用して、Flow から非同期データを収集します。

次のスニペットは、特定のライフサイクル状態でオペレーションを行う方法を説明したものです。

  @Composable
  fun LocationChangedEffect(
    locationManager: LocationManager,
    onLocationChanged: (Location) -> Unit
  ) {
    val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)

    LifecycleStartEffect(locationManager) {
        val listener = LocationListener { newLocation ->
            currentOnLocationChanged(newLocation)
        }

        try {
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000L,
                1f,
                listener,
            )
        } catch (e: SecurityException) {
            // TODO: Handle missing permissions
        }

        onStopOrDispose {
            locationManager.removeUpdates(listener)
        }
    }
  }

依存関係を処理する

コンポーネント間の依存関係を管理する際は、次のベスト プラクティスに従ってください。

推奨事項 説明
依存関係挿入を使用します。 依存関係挿入のベスト プラクティス、可能であれば特にコンストラクタ挿入のベスト プラクティスを活用してください。
必要に応じてコンポーネントにスコープを設定します。 型が共有する必要のある可変データを含む場合、あるいは、型がアプリ内で広く使用されており、その初期化にコストがかかる場合は、依存関係コンテナにスコープを設定します。
Hilt を使用します。 単純なアプリでは、Hilt または手動依存関係挿入を使用します。複雑なプロジェクト(たとえば、次のいずれかを含むプロジェクト)の場合は Hilt を使用します。
  • ViewModel を使用する複数の画面
  • WorkManager を使用する
  • ナビゲーション バックスタックをスコープとする ViewModel がある

テスト

テストを行う際のベスト プラクティスは次のとおりです。

推奨事項 説明
テストする内容を把握する

プロジェクトが「Hello World」アプリのように単純なものでない限り、テストする必要があります。少なくとも次のものを含めます。

  • フローを含む ViewModel の単体テスト
  • データレイヤ エンティティ(リポジトリとデータソース)の単体テスト
  • CI の回帰テストとして役立つ UI ナビゲーション テスト
モックよりもフェイクを優先します。 フェイクの使用について詳しくは、Android でテストダブルを使用するをご覧ください。
StateFlow をテストします。 StateFlow をテストする場合は、次の手順を行います。

詳細については、Android でのテスト項目Compose レイアウトのテストをご覧ください。

モデル

アプリでモデルを開発する場合は、以下のベスト プラクティスを実践してください。

推奨事項 説明
複雑なアプリではレイヤごとにモデルを作成します。

複雑なアプリでは、必要に応じて、別のレイヤやコンポーネントで新しいモデルを作成します。以下の例を考えてみましょう。

  • リモート データソースは、ネットワーク経由で受け取るモデルを、アプリが必要とするデータのみを含むシンプルなクラスにマッピングできます。
  • リポジトリは、UI レイヤが必要とする情報だけで DAO モデルをシンプルなデータクラスにマッピングできます。
  • ViewModel では、UiState クラスにデータレイヤ モデルを含めることができます。

命名規則

コードベースに名前を付けるときは、次のベスト プラクティスに注意する必要があります。

推奨事項 説明
メソッドの命名。
省略可
動詞句を使用してメソッドに名前を付けます(例: makePayment())。
プロパティの命名。
省略可
名詞句を使用してプロパティに名前を付けます(例: inProgressTopicSelection)。
データのストリームの命名。
省略可
クラスが Flow ストリームまたはその他のストリームを公開する場合、命名規則は get{model}Stream です。(例: getAuthorStream(): Flow<Author>)。関数がモデルのリストを返す場合は、複数形のモデル名(getAuthorsStream(): Flow<List<Author>>)を使用します。
インターフェース実装の命名。
省略可
インターフェースの実装にはわかりやすい名前を使用します。適切な名前が見つからない場合は、接頭辞として Default を使用します。たとえば、NewsRepository インターフェースの場合には、OfflineFirstNewsRepository または InMemoryNewsRepository を使用できます。適切な名前が見つからない場合は、DefaultNewsRepository を使用します。FakeAuthorsRepository のように、フェイクの実装には Fake の接頭辞を付けます。

参考情報

Android アーキテクチャの詳細については、以下の参考情報をご覧ください。

ドキュメント

コンテンツの閲覧