web-dev-qa-db-ja.com

Android ViewModel?

編集:この質問は少し古くなっており、GoogleがViewModelをナビゲーショングラフにスコープする機能を提供してくれました。 (アクティビティスコープのモデルをクリアしようとするのではなく)より良いアプローチは、適切な量の画面とそれらにスコープする特定のナビゲーショングラフを作成することです。


Android.Arch.lifecycle.ViewModelクラスを参照します。

ViewModelは、関連するUIコンポーネントのライフサイクルにスコープが設定されているため、Fragmentベースのアプリでは、フラグメントのライフサイクルになります。これは良いことです。


複数のフラグメント間でViewModelインスタンスを共有したい場合があります。特に、多くの画面が同じ基礎データに関連している場合に興味があります。

(ドキュメントは、関連する複数のフラグメントが同じ画面に表示されているときに同様のアプローチを提案していますが、 これは、以下の回答に従って単一のホストフラグメントを使用することで回避できます 。)

これについては、 ViewModelの公式ドキュメント で説明しています。

ViewModelは、アクティビティのさまざまなフラグメント間の通信レイヤーとしても使用できます。各フラグメントは、アクティビティを介して同じキーを使用してViewModelを取得できます。これにより、フラグメント間の通信が分離され、他のフラグメントと直接通信する必要がなくなります。

つまり、異なる画面を表すフラグメント間で情報を共有するには、ViewModelのスコープをActivityライフサイクルに限定する必要があります(Android他の共有インスタンスでも使用できます)。


新しいJetpackナビゲーションパターンでは、「1つのアクティビティ/多くのフラグメント」アーキテクチャを使用することをお勧めします。これは、アプリが使用されている間、アクティビティが存続することを意味します。

つまり、ViewModelライフサイクルを範囲とする共有Activityインスタンスは決してクリアされず、メモリは常に使用されます。

メモリを保持し、いつでも必要なだけ使用するという観点から、不要になった共有ViewModelインスタンスをクリアできると便利です。


ViewModelまたはホルダーフラグメントからViewModelStoreを手動でクリアするにはどうすればよいですか?

46

コードをチェックすると here がわかります。ViewModelStoreViewModelStoreOwnerから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をクリアすることはできません。

12
Nagy Robi

ViewModelActivityライフサイクルにスコープしたくない場合は、親フラグメントのライフサイクルにスコープすることができます。したがって、ViewModelのインスタンスを画面内の複数のフラグメントと共有する場合は、すべてが共通の親フラグメントを共有するようにフラグメントをレイアウトできます。そうすれば、ViewModelをインスタンス化するときに、次のようにすることができます。

CommonViewModel viewModel = ViewModelProviders.of(getParentFragment()).class(CommonViewModel.class);

うまくいけば、これが役立ちます!

5
AvidRP

私にはもっと良い解決策があると思います。

@Nagy Robiが述べたように、viewModelStore.clear()を呼び出すことでViewModelをクリアできます。これの問題は、このViewModelStore内にスコープされたすべてのビューモデルがクリアされることです。つまり、どのViewModelをクリアするかを制御することはできません。

しかし、@ mikehc here によると。代わりに、独自のViewModelStoreを実際に作成することもできます。これにより、ViewModelが存在しなければならないスコープをきめ細かく制御できます。

注:誰もこの方法をとったことはありませんが、これが有効な方法であることを願っています。これは、単一のアクティビティアプリケーションでスコープを制御するための本当に良い方法です。

このアプローチについてフィードバックをお寄せください。何でもよろしくお願いします。

更新:

ナビゲーションコンポーネントv2.1.0-alpha02 であるため、ViewModelsのスコープをフローにできるようになりました。これの欠点は、Navigation Componentをプロジェクトに実装する必要があり、ViewModelのスコープを細かく制御できないことです。しかし、これはより良いことのようです。

4

Navigation Componentライブラリを使用せずにすばやく解決:

getActivity().getViewModelStore().clear();

これにより、Navigation Componentライブラリを組み込まなくてもこの問題が解決されます。また、これは単純な1行のコードです。 ViewModelsを介してFragments間で共有されているActivityをクリアします

4
Sakiboy

私はこの問題に対処するためにライブラリを作成しています: scoped-vm 、それを自由にチェックしてください。フィードバックをいただければ幸いです。内部では、アプローチ @ Archie を使用しています-スコープごとに個別のViewModelStoreを維持しています。しかし、そのスコープからviewmodelを要求した最後のフラグメントが破棄されるとすぐに、さらに一歩進んでViewModelStore自体をクリアします。

現在、ビューモデル管理全体(特にこのライブラリ)は 深刻なバグ の影響を受けており、バックスタックが修正されます。

概要:

  • ViewModel.onCleared()が呼び出されないことを気にする場合、最善の方法は(現時点では)自分でクリアすることです。そのバグのため、fragmentのviewmodelがクリアされるという保証はありません。
  • リークされたViewModelを心配するだけの場合-心配しないでください。他の参照されていないオブジェクトとしてガベージコレクションされます。必要に応じて、私のlibをきめ細かいスコープに自由に使用してください。
1
dhabensky

指摘したように、アーキテクチャコンポーネント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"
                    )
                }
            }
        }
    }
}
_
1
Alex Kuzmin

私の場合、私が観察することのほとんどはViewsに関連しているので、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))
}
1

この問題に対処するためのシンプルでかなりエレガントな方法を見つけました。コツは、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
    }
}   
1
Dustin

私が知っているように、プログラムで手動でViewModelオブジェクトを削除することはできませんが、それに格納されているデータをクリアできます。この場合、これを行うにはOncleared()メソッドを手動で呼び出す必要があります。

  1. ViewModelクラスから拡張されたクラスのOncleared()メソッドをオーバーライドします
  2. このメソッドでは、データを格納するフィールドをnullにすることでデータをクリーンアップできます
  3. データを完全に消去する場合は、このメソッドを呼び出します。
0
Amir Hossein