ViewModel の概要
Android Jetpack の一部。
ViewModel
クラスは、ビジネス ロジックまたは画面レベルの状態ホルダーです。状態を UI に公開し、関連するビジネス ロジックをカプセル化します。状態がキャッシュに保存され、構成が変更されてもそれが維持されることが主なメリットです。つまり、アクティビティ間を移動するときや、画面の回転などの構成の変更に従うときに、UI でデータを再度取得する必要がありません。
状態ホルダーの詳細については、状態ホルダーのガイダンスをご覧ください。また、UI レイヤの一般的情報については、UI レイヤのガイダンスをご覧ください。
ViewModel のメリット
ViewModel の代わりに、UI に表示するデータを保持するプレーンクラスを利用できます。これは、アクティビティ間や Navigation デスティネーション間を移動する際に問題になることがあります。インスタンス状態保存メカニズムを使用して保存せずにこれを行うと、そのデータが破棄されます。ViewModel であれば、データの永続性のための便利な API を利用して、この問題を解決できます。
ViewModel クラスの主なメリットは基本的に次の 2 つです。
- UI 状態を保持できます。
- ビジネス ロジックにアクセスできます。
永続性
ViewModel は、ViewModel で保持される状態と、ViewModel でトリガーされるオペレーションの両方で、永続性を実現します。このようにキャッシュに保存されることで、一般的な構成の変更(画面回転など)があってもデータを再度取得する必要がなくなります。
範囲
ViewModel をインスタンス化する場合、ViewModelStoreOwner
インターフェースを実装するオブジェクトを渡します。これは、Navigation デスティネーション、Navigation グラフ、アクティビティ、フラグメント、その他のインターフェースを実装するなんらかのタイプになります。これにより、ViewModelStoreOwner
のライフサイクルに ViewModel のスコープが設定されます。これは、ViewModelStoreOwner
が完全に削除されるまでメモリ内に残ります。
クラスの範囲は、ViewModelStoreOwner
インターフェースの直接または間接のサブクラスです。直接サブクラスは、ComponentActivity
、Fragment
、NavBackStackEntry
です。間接サブクラスの完全なリストについては、ViewModelStoreOwner
リファレンスをご覧ください。
ViewModel がスコープされているフラグメントまたはアクティビティが破棄されると、スコープされている ViewModel では非同期処理が続行されます。これが永続性の鍵となります。
詳しくは、ViewModel のライフサイクルに関する以下のセクションをご覧ください。
SavedStateHandle
SavedStateHandle を使用すると、構成の変更でのみならず、プロセスの再作成後もデータを保持できます。つまり、ユーザーがアプリを閉じてから後で開いた場合でも、UI の状態を維持できます。
ビジネス ロジックへのアクセス
ビジネス ロジックの大部分はデータレイヤに存在しますが、UI レイヤにビジネス ロジックも含めることができます。これは、複数のリポジトリからのデータを組み合わせて画面 UI の状態を作成する場合や、特定の種類のデータにデータレイヤが不要な場合などに該当します。
ViewModel は、UI レイヤでビジネス ロジックを処理するのに適した場所です。また、アプリデータの変更のためにビジネス ロジックを適用する必要がある場合、ViewModel はイベントの処理も担い、階層の他のレイヤに委任します。
Jetpack Compose
Jetpack Compose を使用する場合、ViewModel は、コンポーザブルに画面 UI の状態を公開する主要な手段です。ハイブリッド アプリの場合、アクティビティとフラグメントでコンポーズ可能な関数をホストするだけです。これは、アクティビティやフラグメントで再利用可能な UI を作成することがそれほど単純で直感的ではなかった以前のアプローチからシフトしたものであり、UI コントローラとしてはるかに有効になりました。
Compose で ViewModel を使用する場合に理解すべき最も重要なことは、ViewModel のスコープをコンポーザブルに設定できないことです。これは、コンポーザブルが ViewModelStoreOwner
ではないためです。コンポジション内の同じコンポーザブルの 2 つのインスタンス、または同じ ViewModelStoreOwner
の同じ ViewModel タイプにアクセスする 2 つの異なるコンポーザブルは、ViewModel の同じインスタンスを受け取ります。通常は、このような動作は想定されたものではありません。
Compose で ViewModel からメリットを得るには、各画面をフラグメントまたはアクティビティでホストするか、Compose Navigation を使用して、Navigation デスティネーションにできるだけ近いコンポーズ可能な関数で ViewModel を使用します。これは、ViewModel のスコープを Navigation デスティネーション、Navigation グラフ、アクティビティ、フラグメントに設定できるためです。
詳しくは、Jetpack Compose の状態ホイスティングに関するガイドをご覧ください。
ViewModel を実装する
ユーザーがサイコロを振ることができる画面の ViewModel の実装例を次に示します。
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Handle business logic
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
public class DiceUiState {
private final Integer firstDieValue;
private final Integer secondDieValue;
private final int numberOfRolls;
// ...
}
public class DiceRollViewModel extends ViewModel {
private final MutableLiveData<DiceUiState> uiState =
new MutableLiveData(new DiceUiState(null, null, 0));
public LiveData<DiceUiState> getUiState() {
return uiState;
}
public void rollDice() {
Random random = new Random();
uiState.setValue(
new DiceUiState(
random.nextInt(7) + 1,
random.nextInt(7) + 1,
uiState.getValue().getNumberOfRolls() + 1
)
);
}
}
次のように、ViewModel にはアクティビティからアクセスできます。
import androidx.activity.viewModels
class DiceRollActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same DiceRollViewModel instance created by the first activity.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
val viewModel: DiceRollViewModel by viewModels()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// Update UI elements
}
}
}
}
}
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
model.getUiState().observe(this, uiState -> {
// update UI
});
}
}
import androidx.lifecycle.viewmodel.compose.viewModel
// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
viewModel: DiceRollViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Update UI elements
}
ViewModel でコルーチンを使用する
ViewModel
には、Kotlin コルーチンのサポートが含まれています。UI 状態を永続化する場合と同じ方法で、非同期処理を永続化できます。
詳しくは、Android アーキテクチャ コンポーネントで Kotlin コルーチンを使用するをご覧ください。
ViewModel のライフサイクル
ViewModel
のライフサイクルは、そのスコープに直接関連付けられます。ViewModel
は、スコープに設定されている ViewModelStoreOwner
が消えるまでメモリ内にとどまります。これは、次のような状況で発生します。
- アクティビティの場合は終了時。
- フラグメントの場合はデタッチ時。
- Navigation エントリの場合はバックスタックからの削除時。
これにより、ViewModel は、構成の変更後に引き継ぐデータを保存するための優れたソリューションとなっています。
図 1 に、回転を経て終了するまでのアクティビティのさまざまなライフサイクルの状態を示します。この図には、関連するアクティビティのライフサイクルの横に ViewModel
のライフタイムも示されています。この図はアクティビティの状態を示していますが、フラグメントのライフサイクルの状態も基本的には同じです。
通常は、アクティビティ オブジェクトの onCreate()
メソッドが最初に呼び出されたときに、ViewModel
をリクエストします。onCreate()
は、デバイスの画面が回転されたときなど、アクティビティの存続期間全体を通して複数回呼び出されることがあります。ViewModel
は、
まず、アクティビティが終了して破棄されるまで、ViewModel
をリクエストします。
ViewModel の依存関係をクリアする
ViewModel は、ライフサイクル中に ViewModelStoreOwner
によって破棄されると、onCleared
メソッドを呼び出します。これにより、ViewModel のライフサイクルに従う処理や依存関係をクリーンアップできます。
次の例は、viewModelScope
に代わるものです。viewModelScope
は、ViewModel のライフサイクルに自動的に従う、組み込みの CoroutineScope
です。これを使用して、ViewModel はビジネス関連のオペレーションをトリガーします。テストを容易にするために viewModelScope
ではなくカスタム スコープを使用する場合、ViewModel はコンストラクタの依存関係として CoroutineScope
を受け取ることができます。ViewModelStoreOwner
がライフサイクルの終了時に ViewModel をクリアすると、ViewModel も CoroutineScope
をキャンセルします。
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
ライフサイクル バージョン 2.5 以降では、ViewModel インスタンスがクリアされると自動的に終了する ViewModel のコンストラクタに 1 つ以上の Closeable
オブジェクトを渡すことができます。
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
class MyViewModel(
private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
// Other ViewModel logic ...
}
ベスト プラクティス
ViewModel を実装する際は、次の主要なベスト プラクティスを行う必要があります。
- スコープ設定のため、ViewModel を画面レベルの状態ホルダーの実装の詳細として使用します。再利用可能な UI コンポーネント(チップグループやフォームなど)の状態ホルダーとしては使用しないでください。そうでない場合は、 同じ UI コンポーネントの異なる用途での ViewModel インスタンス ViewModelStoreOwner(チップごとに明示的なビューモデルキーを使用する場合を除きます)。
- ViewModel が、UI 実装の詳細を認識しないようにします。ViewModel API が公開するメソッドの名前と、UI の状態フィールドの名前は、できるだけ汎用的なものにしてください。このようにすることで、ViewModel があらゆる種類の UI(スマートフォン、折りたたみ式デバイス、タブレット、Chromebook)に対応できるようになります。
- ViewModel は
ViewModelStoreOwner
よりも長く存続する可能性があるため、 ライフサイクル関連の API(Context
など)への参照を保持しないようにする必要があります。 またはResources
を使用してメモリリークを防ぎます。 - ViewModel を他のクラス、関数、その他の UI コンポーネントに渡さないようにします。プラットフォームで管理するため、可能な限りプラットフォームの近くに置く必要があります。アクティビティ、フラグメント、画面レベルのコンポーズ可能な関数の近くです。これにより、下位レベルのコンポーネントが必要以上にデータやロジックにアクセスすることを防止できます。
追加情報
データの複雑さが増すと、データの読み込みのためだけに別のクラスを使用することがあります。ViewModel
の目的は、UI コントローラのデータをカプセル化して、構成の変更後にもデータが引き継がれるようにすることです。構成の変更の前後におけるデータの読み込み、永続化、管理の方法については、保存された UI の状態をご覧ください。
Android アプリのアーキテクチャ ガイドでは、これらの機能を処理するためにリポジトリ クラスを作成することが推奨されています。
参考情報
ViewModel
クラスについて詳しくは、以下のリソースをご覧ください。
ドキュメント
サンプル
Android 8.0 では、アクティビティをピクチャー イン ピクチャー(PIP)モードで起動できます 通知は、アプリの UI の外で Android が表示するメッセージであり、リマインダー、他の人からのメッセージ、アプリからのタイムリーな情報などをユーザーに提供します。ユーザーは通知をタップしてアプリを開いたり、通知から直接操作したりできます。 このページでは、通知が表示される場所と利用できる機能について概説します。通知の作成を開始するには、 通知を作成する をご覧ください。 通知の設計と操作パターンについて詳しくは、 通知のデザインガイド をご覧ください。 非明示的ブロードキャストに対する新しい制限。 ユーザーは多くの場合、絵文字やステッカーなど、さまざまな情報を使用してコミュニケーションを
説明します。以前のバージョンの Android では、ソフト キーボード(別名「ソフト キーボード」)が インプット メソッド エディタ IME - Unicode 絵文字のみをアプリに送信できます。リッチ コンテンツの場合、
他のアプリで使用できない、または次のような回避策を使用したアプリ固有の API シンプルな共有操作 による画像の送信
アクセスできます。 Android 7.1(API レベル TV 入力サービスを使用すると、ユーザーは タイムシフト API 。
タイムシフトを拡張する Android 7.0
ユーザーが複数の録画セッションを保存できるようにします 事前に録画を予約したり、視聴しながら録画を開始したりできます。
できます。システムで録音を保存すると、ユーザーはその録音のブラウジング、管理、
システムの TV アプリを使用して録画を再生します。 TV Android 7.0(API レベル 24)以降、Android では多言語ユーザー向けのサポートが強化され、ユーザーは設定で複数の言語 / 地域を選択できるようになっています。Android は、サポートするロケール数を大幅に増やし、システムがリソースを解決する方法を変更することで、この機能を実現しています。 このドキュメントでは、まず Android 7.0(API レベル 24)より前のバージョンにおけるリソース解決戦略について説明します。次に、Android 7.0 アプリ デベロッパーが安全な構成ファイルでネットワーク セキュリティ設定をカスタマイズできる機能。 Android 7.0 は、デバイスのロックを解除していない状態でユーザーがデバイスの電源を入れた場合、セキュアなダイレクト ブート モードで動作します。この機能をサポートするため、システムは次の 2 つのデータ保存先を備えています。 デフォルトでは、ダイレクト ブート モード中にアプリは実行されません。ダイレクト ブート モード中にアプリの稼働が必要な場合は、このモード中でも実行するアプリ コンポーネントを登録します。ダイレクト ブート Learn how to add shortcuts to specific actions within your app.ピクチャー イン ピクチャー(PIP)を使って動画を追加する
通知の概要
バックグラウンドでの最適化
イメージ キーボードのサポート
コンテンツの録画をサポートする
言語と地域の解決の概要
ネットワーク セキュリティ構成
ダイレクト ブート モードをサポートする
App shortcuts overview
現在、おすすめはありません。
Google アカウントにログインしてください。