web-dev-qa-db-ja.com

Android ViewModel呼び出しアクティビティメソッド

プロジェクトでAndroid AACライブラリとAndroidデータバインディングライブラリを使用しています。AuthActivityがあり、AuthViewModelがAndroidのViewModelクラスを拡張しています。場合によっては質問する必要があります。アクティビティがViewModelのいくつかのメソッドを呼び出すため。たとえば、ユーザーがアクティビティクラスで初期化されたGoogleAuthまたはFacebookAuthボタンをクリックすると(GoogleApiClientを初期化するには、ViewModelに渡せないActivityコンテキストが必要なため、ViewModelはActivityを保存できません。フィールド)。Activityクラスに実装されたGoogleApiおよびFacebookAPIのすべてのロジック:

//google api initialization
googleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this, this)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

//facebook login button
loginButton.setReadPermissions(Arrays.asList("email", "public_profile"));
loginButton.registerCallback(callbackManager,

また、アクティビティコンテキストも必要とするサインインインテントを呼び出す必要があります。

Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(signInIntent, GOOGLE_AUTH);

FacebookログインとGoogleログイン、またはビューモデルクラスからstartActivityインテントをリクエストできないため、クラスインターフェイスAuthActivityListenerを作成しました。

public interface AuthActivityListener {
    void requestSignedIn();

    void requestGoogleAuth();

    void requestFacebookAuth();

    void requestShowDialogFragment(int type);
}

アクティビティクラスにリスナーを実装します。

AuthActivityRequester authRequestListener = new AuthActivityRequester() {
        @Override
        public void requestSignedIn() {
            Intent intent = new Intent(AuthActivity.this, ScanActivity.class);
            startActivity(intent);
            AuthActivity.this.finish();
        }

        @Override
        public void requestGoogleAuth() {
            Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
            startActivityForResult(signInIntent, GOOGLE_AUTH);
        }
        ...

そして、ビューモデルクラスでこのリスナーを割り当てて、アクティビティメソッドを呼び出します。

// in constructor
this.authRequester = listener;

// call activity method
public void onClickedAuthGoogle() {
        authRequester.requestGoogleAuth();
}

グーグルまたはフェイスブックの認証に合格した後、アクティビティからビューモデルメソッドを呼び出します。

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        callbackManager.onActivityResult(requestCode, resultCode, data);
        if (requestCode == GOOGLE_AUTH) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            if (result.isSuccess()) {
                GoogleSignInAccount acct = result.getSignInAccount();
                if (acct != null) {
                    viewModel.onGoogleUserLoaded(acct.getEmail(), acct.getId());
                } else {
                    viewModel.onGoogleUserLoaded("", "");
                }
            }
        }
    }

ビューモデルとアクティビティの間の通信のこのアプローチは正しいのか、ビューモデルからアクティビティメソッドを呼び出す別の方法を見つける必要があるのか​​、誰かが私に説明できますか?

12
Vlad Morzhanov

mVVMの最も難しい部分はビューモデルはビューについて認識してはならず、それらを参照してはなりません

これは非常に強い制限です。

あなたはそれについていくつかのオプションがあります

1。コンテキスト引数を受け取るモデルメソッドを表示します

ビューからコンテキストを受け取るメソッドを作成できます(このメソッドはビューから呼び出されます)。

コンテキスト関連の変数をインスタンス化できた後。

メモリリークに気付いた場合は、ビューが一時停止しているときに破棄するか、ライフサイクル対応AACの使用を停止し、アクティビティまたはフラグメントの再開または開始時に元に戻します。

OnActivityResultについては、APIサポートがそのようなものであるため、ソリューションは悪くないと思います。

2。データバインディングを使用してビューからコンテキストを取得します

レイアウトxmlでは、イベントリスナーを使用してビュー自体を送信できます。

<Button
    ....
    Android:onClick=“@{(view) -> vm.onClickFacebookLogin(view)}”

次に、ビューを受け取り、Viewmodelのビューからコンテキストを取得できます

。AndroidViewModelを使用

AndroidViewModelクラスは、アプリケーションコンテキストがないViewModelクラスと同じです。

アプリケーションコンテキストは次のように使用できます

gerApplication()

ありがとうございました

1
MJ Studio

これを行う方法にはいくつかの異なるアプローチがあります。ここで私はあなたと私のアプローチを共有したいと思います。私の意見では、これはMVVMパターンのイデオロギーに最も適しています。

前述のように、「ビューモデルはビューについて何も知らず、それを参照する必要があります」。これにより、ビューモデルがActivityメソッドを呼び出す方法について多くのオプションが残されません。まず、頭に浮かぶのはListenerアプローチです。しかし、このアプローチには、私の意見ではいくつかの欠点があります。

  • Viewは、ViewModelよりも有効期間が短い可能性が高いため、ViewModelへの/からのサブスクライブ/サブスクライブ解除を処理する必要があります。
  • 最初の欠点は、何かが発生し、ViewModelViewのメソッドを呼び出す必要があるが、Viewがサブスクライブ/サブスクライブ解除の間にあるという状況にもつながります。 ViewModelは、nullである可能性があるため、空のリスナーの状況にも注意する必要があります。
  • ViewModel-Activity通信の新しいメソッドを追加する場合は、ViewModelActivity、およびListenerインターフェイスを変更する必要があります。

したがって、Listenerアプローチはあまり適していません。そしてそれはMVPアプローチのように見えます。上記の欠点(または少なくともそれらのいくつか)を排除するために、私が作成した、ViewModel Eventsアプローチを作成しました。このアプローチでは、ViewModelはイベントを「発行」(または生成)し、Viewにそれらを監視させます。私が話していることを示しましょう。

最初に、ViewModelイベントの表現が必要になります。

_abstract class ViewModelEvent {
    var handled: Boolean = false
        private set

    fun handle(activity: BaseActivity) {
        handled = true
    }
}
_

すでにご覧のとおり、handle()メソッドが魔法を実行します。 Activityが受信したイベントを処理するとき、そのインスタンスをhandle()メソッドにパラメーターとして渡します。このメソッド内で、任意のActivityメソッドを呼び出すことができます(または特定のActivityに安全にキャストできます)。 handledプロパティは、ActivityがこのViewModelEventを2回処理しないようにすることを目的としています。

さらに、ViewModelがそのイベントを発行するためのメカニズムを作成する必要があります。 LiveDataは、これらのニーズに最も適しています。ライフサイクルイベントのオブザーバーサブスクリプションをキャンセルし、最後に発行されたイベントを保存します(そのため、ViewModelEventには上記のhandledプロパティが必要です)。

_abstract class BaseViewModel: ViewModel() {
    private val observableEvents = MutableLiveData<ViewModelEvent>()

    fun observeViewModelEvents(): LiveData<ViewModelEvent> = observableEvents

    protected fun postViewModelEvent(event: ViewModelEvent) {
        observableEvents.postValue(event)
    }
}
_

ここでは複雑なことは何もありません。 MutableLiveDataLiveDataとして公開)とイベントを発行するメソッドだけです。ちなみに、postViewModelEvent内では、このメソッドが呼び出されたスレッドを確認し、_MutableLiveData.postValue_または_MutableLiveData.setValue_を使用できます。

そして最後に、Activity自体。

_abstract class BaseActivity: Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        viewModel.observeViewModelEvents().observe(this, Observer {
            val event = it.takeUnless { it == null || it.handled } ?: return@Observer
            handleViewModelAction(event)
        })
    }

    protected open fun handleViewModelAction(event: ViewModelEvent) {
        event.handle(this)
    }
}
_

ご覧のとおり、一般的なイベントはBaseActivityで処理できますが、一部の特定のイベントはhandleViewModelActionメソッドをオーバーライドすることで処理できます。

このアプローチは、特定のニーズに合わせて変更できます。たとえば、ViewModelEventActivityインスタンスを操作する必要はなく、「マーカー」イベントとして使用したり、必要なアクションなどの特定のパラメーターを渡すことができます。

ViewModel Eventsアプローチにより、ViewModel-Activity通信が堅牢かつシームレスになります。 Activityは一度サブスクライブする必要があり、最新のViewModelのイベントを見逃すことはありません。

0
Demigod