このページでは、アーキテクチャのベスト プラクティスと推奨事項をいくつか紹介します。これらを採用することで、アプリの品質、堅牢性、スケーラビリティを向上できます。また、アプリのメンテナンスとテストも容易になります。
以下のベスト プラクティスは、トピック別にグループ化されています。それぞれに、推奨事項の強さを示す優先度があります。優先度のリストは次のとおりです。
- 強く推奨: 自身の手法と根本的に対立する場合を除き、このプラクティスを実装します。
- 推奨: このプラクティスで、アプリを改善できる可能性があります。
- 省略可: このプラクティスで、特定の状況下でアプリを改善できる可能性があります。
階層型アーキテクチャ
Google が推奨する階層型アーキテクチャでは、関心の分離を重視しています。データモデルから UI を動作させ、信頼できる唯一の情報源の原則を遵守し、単方向データフローの原則に従います。階層型アーキテクチャに関するベスト プラクティスは次のとおりです。
| 推奨事項 | 説明 |
|---|---|
| 明確に定義されたデータレイヤを使用します 強く推奨 |
データレイヤは、アプリデータをアプリの他の部分に公開し、アプリのビジネス ロジックの大部分を含みます。
|
| 明確に定義された UI レイヤを使用します。
強く推奨 |
UI レイヤは、アプリデータを画面に表示するもので、ユーザー インタラクションの主要なポイントとして機能します。Jetpack Compose は、アプリの UI をビルドする際に推奨される最新のツールキットです。
|
| リポジトリを使用して、データレイヤからアプリケーション データを公開します。 強く推奨 |
コンポーザブルや ViewModel などの UI レイヤのコンポーネントを、データソースと直接やり取りさせないようにします。データソースの例:
|
| コルーチンと 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 の状態を収集します。詳しくは、 |
| 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 で、ライフサイクルに関連する型への参照を保持しないようにします。Activity、Context、Resources を依存関係として渡さないようにします。ViewModel で Context を必要とする場合は、それが適切なレイヤにあるかどうかを慎重に評価してください。 |
| コルーチンと Flow を使用します。 強く推奨 |
ViewModel は、以下を使用してデータレイヤまたはドメインレイヤとやり取りします。
|
| 画面レベルで ViewModel を使用します。 強く推奨 |
再利用可能な UI で ViewModel を使用しないようにします。ViewModel は、以下で使用します。
|
| 再利用可能な UI コンポーネントでは、プレーンな状態ホルダークラスを使用します。 強く推奨 |
再利用可能な UI コンポーネントの複雑さに対処するには、プレーンな状態ホルダークラスを使用します。これにより、状態を外部でホイスティングして制御できるようになります。 |
AndroidViewModel を使用しないようにします。推奨 |
AndroidViewModel ではなく ViewModel クラスを使用します。ViewModel で Application クラスを使用しないでください。代わりに、依存関係を UI またはデータレイヤに移行します。 |
| UI 状態を公開します。
推奨 |
ViewModel が、uiState という単一のプロパティを介して UI にデータを公開するようにします。UI に互いに関係のない複数のデータが表示されている場合、VM が複数の UI 状態プロパティを公開する可能性があります。
|
次のスニペットは、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 関連のタスクを実行するために、
|
次のスニペットは、特定のライフサイクル状態でオペレーションを行う方法を説明したものです。
@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 を使用します。
|
テスト
テストを行う際のベスト プラクティスは次のとおりです。
| 推奨事項 | 説明 |
|---|---|
| テストする内容を把握する。 強く推奨 |
プロジェクトが「Hello World」アプリのように単純なものでない限り、テストする必要があります。少なくとも次のものを含めます。
|
| モックよりもフェイクを優先します。 強く推奨 |
フェイクの使用について詳しくは、Android でテストダブルを使用するをご覧ください。 |
| StateFlow をテストします。 強く推奨 |
StateFlow をテストする場合は、次の手順を行います。
|
詳細については、Android でのテスト項目と Compose レイアウトのテストをご覧ください。
モデル
アプリでモデルを開発する場合は、以下のベスト プラクティスを実践してください。
| 推奨事項 | 説明 |
|---|---|
| 複雑なアプリではレイヤごとにモデルを作成します。 推奨 |
複雑なアプリでは、必要に応じて、別のレイヤやコンポーネントで新しいモデルを作成します。以下の例を考えてみましょう。
|
命名規則
コードベースに名前を付けるときは、次のベスト プラクティスに注意する必要があります。
| 推奨事項 | 説明 |
|---|---|
| メソッドの命名。 省略可 |
動詞句を使用してメソッドに名前を付けます(例: makePayment())。 |
| プロパティの命名。 省略可 |
名詞句を使用してプロパティに名前を付けます(例: inProgressTopicSelection)。 |
| データのストリームの命名。 省略可 |
クラスが Flow ストリームまたはその他のストリームを公開する場合、命名規則は get{model}Stream です。(例: getAuthorStream(): Flow<Author>)。関数がモデルのリストを返す場合は、複数形のモデル名(getAuthorsStream(): Flow<List<Author>>)を使用します。 |
| インターフェース実装の命名。 省略可 |
インターフェースの実装にはわかりやすい名前を使用します。適切な名前が見つからない場合は、接頭辞として Default を使用します。たとえば、NewsRepository インターフェースの場合には、OfflineFirstNewsRepository または InMemoryNewsRepository を使用できます。適切な名前が見つからない場合は、DefaultNewsRepository を使用します。FakeAuthorsRepository のように、フェイクの実装には Fake の接頭辞を付けます。 |
参考情報
Android アーキテクチャの詳細については、以下の参考情報をご覧ください。