編集:この質問は少し古くなっており、GoogleがViewModel
をナビゲーショングラフにスコープする機能を提供してくれました。 (アクティビティスコープのモデルをクリアしようとするのではなく)より良いアプローチは、適切な量の画面とそれらにスコープする特定のナビゲーショングラフを作成することです。
Android.Arch.lifecycle.ViewModel
クラスを参照します。
ViewModel
は、関連するUIコンポーネントのライフサイクルにスコープが設定されているため、Fragment
ベースのアプリでは、フラグメントのライフサイクルになります。これは良いことです。
複数のフラグメント間でViewModel
インスタンスを共有したい場合があります。特に、多くの画面が同じ基礎データに関連している場合に興味があります。
(ドキュメントは、関連する複数のフラグメントが同じ画面に表示されているときに同様のアプローチを提案していますが、 これは、以下の回答に従って単一のホストフラグメントを使用することで回避できます 。)
これについては、 ViewModelの公式ドキュメント で説明しています。
ViewModelは、アクティビティのさまざまなフラグメント間の通信レイヤーとしても使用できます。各フラグメントは、アクティビティを介して同じキーを使用してViewModelを取得できます。これにより、フラグメント間の通信が分離され、他のフラグメントと直接通信する必要がなくなります。
つまり、異なる画面を表すフラグメント間で情報を共有するには、ViewModel
のスコープをActivity
ライフサイクルに限定する必要があります(Android他の共有インスタンスでも使用できます)。
新しいJetpackナビゲーションパターンでは、「1つのアクティビティ/多くのフラグメント」アーキテクチャを使用することをお勧めします。これは、アプリが使用されている間、アクティビティが存続することを意味します。
つまり、ViewModel
ライフサイクルを範囲とする共有Activity
インスタンスは決してクリアされず、メモリは常に使用されます。
メモリを保持し、いつでも必要なだけ使用するという観点から、不要になった共有ViewModel
インスタンスをクリアできると便利です。
ViewModel
またはホルダーフラグメントからViewModelStore
を手動でクリアするにはどうすればよいですか?
コードをチェックすると here がわかります。ViewModelStore
とViewModelStoreOwner
からFragment
を取得できることがわかります。FragmentActivity
は、たとえば、そのインターフェースを実装します。
そこからSooを呼び出すと、viewModelStore.clear()
を呼び出すことができます。
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
N.B.:これにより、特定のLifeCycleOwnerで使用可能なすべてのViewModelがクリアされます。これにより、1つの特定のViewModelをクリアすることはできません。
ViewModel
をActivity
ライフサイクルにスコープしたくない場合は、親フラグメントのライフサイクルにスコープすることができます。したがって、ViewModel
のインスタンスを画面内の複数のフラグメントと共有する場合は、すべてが共通の親フラグメントを共有するようにフラグメントをレイアウトできます。そうすれば、ViewModel
をインスタンス化するときに、次のようにすることができます。
CommonViewModel viewModel = ViewModelProviders.of(getParentFragment()).class(CommonViewModel.class);
うまくいけば、これが役立ちます!
私にはもっと良い解決策があると思います。
@Nagy Robiが述べたように、viewModelStore.clear()
を呼び出すことでViewModel
をクリアできます。これの問題は、このViewModelStore
内にスコープされたすべてのビューモデルがクリアされることです。つまり、どのViewModel
をクリアするかを制御することはできません。
しかし、@ mikehc here によると。代わりに、独自のViewModelStore
を実際に作成することもできます。これにより、ViewModelが存在しなければならないスコープをきめ細かく制御できます。
注:誰もこの方法をとったことはありませんが、これが有効な方法であることを願っています。これは、単一のアクティビティアプリケーションでスコープを制御するための本当に良い方法です。
このアプローチについてフィードバックをお寄せください。何でもよろしくお願いします。
ナビゲーションコンポーネントv2.1.0-alpha02 であるため、ViewModel
sのスコープをフローにできるようになりました。これの欠点は、Navigation Component
をプロジェクトに実装する必要があり、ViewModel
のスコープを細かく制御できないことです。しかし、これはより良いことのようです。
Navigation Component
ライブラリを使用せずにすばやく解決:
getActivity().getViewModelStore().clear();
これにより、Navigation Component
ライブラリを組み込まなくてもこの問題が解決されます。また、これは単純な1行のコードです。 ViewModels
を介してFragments
間で共有されているActivity
をクリアします
私はこの問題に対処するためにライブラリを作成しています: scoped-vm 、それを自由にチェックしてください。フィードバックをいただければ幸いです。内部では、アプローチ @ Archie を使用しています-スコープごとに個別のViewModelStoreを維持しています。しかし、そのスコープからviewmodelを要求した最後のフラグメントが破棄されるとすぐに、さらに一歩進んでViewModelStore自体をクリアします。
現在、ビューモデル管理全体(特にこのライブラリ)は 深刻なバグ の影響を受けており、バックスタックが修正されます。
概要:
ViewModel.onCleared()
が呼び出されないことを気にする場合、最善の方法は(現時点では)自分でクリアすることです。そのバグのため、fragment
のviewmodelがクリアされるという保証はありません。ViewModel
を心配するだけの場合-心配しないでください。他の参照されていないオブジェクトとしてガベージコレクションされます。必要に応じて、私のlibをきめ細かいスコープに自由に使用してください。指摘したように、アーキテクチャコンポーネントAPIを使用してViewModelStoreの個々のViewModelをクリアすることはできません。この問題の考えられる解決策の1つは、必要なときに安全にクリアできるViewModelごとのストアを用意することです。
_class MainActivity : AppCompatActivity() {
val individualModelStores = HashMap<KClass<out ViewModel>, ViewModelStore>()
inline fun <reified VIEWMODEL : ViewModel> getSharedViewModel(): VIEWMODEL {
val factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
//Put your existing ViewModel instantiation code here,
//e.g., dependency injection or a factory you're using
//For the simplicity of example let's assume
//that your ViewModel doesn't take any arguments
return modelClass.newInstance()
}
}
val viewModelStore = [email protected]<VIEWMODEL>()
return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.Java)
}
val viewModelStore = [email protected]<VIEWMODEL>()
return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.Java)
}
inline fun <reified VIEWMODEL : ViewModel> getIndividualViewModelStore(): ViewModelStore {
val viewModelKey = VIEWMODEL::class
var viewModelStore = individualModelStores[viewModelKey]
return if (viewModelStore != null) {
viewModelStore
} else {
viewModelStore = ViewModelStore()
individualModelStores[viewModelKey] = viewModelStore
return viewModelStore
}
}
inline fun <reified VIEWMODEL : ViewModel> clearIndividualViewModelStore() {
val viewModelKey = VIEWMODEL::class
individualModelStores[viewModelKey]?.clear()
individualModelStores.remove(viewModelKey)
}
_
}
getSharedViewModel()
を使用して、アクティビティのライフサイクルにバインドされているViewModelのインスタンスを取得します。
_val yourViewModel : YourViewModel = (requireActivity() as MainActivity).getSharedViewModel(/*There could be some arguments in case of a more complex ViewModelProvider.Factory implementation*/)
_
後で、共有ViewModelを破棄するときが来たら、clearIndividualViewModelStore<>()
を使用します。
_(requireActivity() as MainActivity).clearIndividualViewModelStore<YourViewModel>()
_
不要になった場合は、ViewModelをできるだけ早くクリアしたい場合があります(たとえば、ユーザー名やパスワードなどの機密性の高いユーザーデータが含まれている場合)。フラグメントの切り替えごとにindividualModelStores
の状態をログに記録して、共有ViewModelを追跡できるようにする方法を次に示します。
_override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (BuildConfig.DEBUG) {
navController.addOnDestinationChangedListener { _, _, _ ->
if (individualModelStores.isNotEmpty()) {
val tag = [email protected]
Log.w(
tag,
"Don't forget to clear the shared ViewModelStores if they are not needed anymore."
)
Log.w(
tag,
"Currently there are ${individualModelStores.keys.size} ViewModelStores bound to ${[email protected]}:"
)
for ((index, viewModelClass) in individualModelStores.keys.withIndex()) {
Log.w(
tag,
"${index + 1}) $viewModelClass\n"
)
}
}
}
}
}
_
私の場合、私が観察することのほとんどはView
sに関連しているので、View
が破壊された場合(Fragment
ではない)をクリアする必要はありません。
別のLiveData
に移動するFragment
のようなものが必要な場合(または処理を1回だけ行う場合)は、「消費オブザーバー」を作成します。
これは、MutableLiveData<T>
を拡張することで実行できます。
fun <T> MutableLiveData<T>.observeConsuming(viewLifecycleOwner: LifecycleOwner, function: (T) -> Unit) {
observe(viewLifecycleOwner, Observer<T> {
function(it ?: return@Observer)
value = null
})
}
観察されるとすぐに、LiveData
から消去されます。
これで、次のように呼び出すことができます。
viewModel.navigation.observeConsuming(viewLifecycleOwner) {
startActivity(Intent(this, LoginActivity::class.Java))
}
この問題に対処するためのシンプルでかなりエレガントな方法を見つけました。コツは、DummyViewModelとモデルキーを使用することです。
AndroidXがget()でモデルのクラス型をチェックするため、コードは機能します。一致しない場合は、現在のViewModelProvider.Factoryを使用して新しいViewModelを作成します。
public class MyActivity extends AppCompatActivity {
private static final String KEY_MY_MODEL = "model";
void clearMyViewModel() {
new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).
.get(KEY_MY_MODEL, DummyViewModel.class);
}
MyViewModel getMyViewModel() {
return new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication()).
.get(KEY_MY_MODEL, MyViewModel.class);
}
static class DummyViewModel extends ViewModel {
//Intentionally blank
}
}
私が知っているように、プログラムで手動でViewModelオブジェクトを削除することはできませんが、それに格納されているデータをクリアできます。この場合、これを行うにはOncleared()
メソッドを手動で呼び出す必要があります。
ViewModel
クラスから拡張されたクラスのOncleared()
メソッドをオーバーライドします