Android ViewModel call Виды деятельности

Я использую в своем проекте библиотеку Android AAC и библиотеку привязки данных Android. У меня AuthActivity и AuthViewModel расширяет класс android ViewModel. В некоторых случаях мне нужно попросить Activity вызвать некоторые методы для ViewModel. Например, когда пользователь нажимает кнопку Google Auth или Facebook Auth, которая инициализируется в классе Activity (потому что для инициализации GoogleApiClient мне нужен контекст действия, который я не могу передать в ViewModel, модель просмотра не может сохранять поля Activity). Вся логика с API Google Api и Facebook реализована в классе Activity:

//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,

Также мне нужно вызвать функцию sign, которая требует контекста Activity:

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();
}

После того, как аутентификация google или facebook прошла, я вызываю метод модели вызова из активности:

@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("", "");
                }
            }
        }
    }

Может ли кто-нибудь объяснить мне, является ли такой подход связи между моделью представления и активностью правильным, или мне нужно найти другой способ вызова методов деятельности из модели представления?

Ответ 1

самая сложная часть MVVM - модель представления не должна знать о представлении и ссылаться на них

Это довольно сильное ограничение.

У вас есть несколько вариантов об этом

1. Просмотр методов модели, получающих аргумент контекста

Вы можете создавать методы, получающие контекст из представления (этот метод вызывается из представления).

После вы можете создать экземпляр переменных, связанных с контекстом.

Если вам известно об утечке памяти, просто уничтожьте ее, когда просмотр приостановлен, или прекратите использование AAC с поддержкой жизненного цикла и восстановите его при возобновлении или начале действия или фрагмента.

Что касается onActivityResult, я думаю, что ваше решение неплохое, потому что поддержка API такая.

2. получить контекст из представления с привязкой данных

в макете XML вы можете отправлять представление непосредственно с помощью прослушивателя событий.

<Button
    ....
    android:onClick='@{(view) -> vm.onClickFacebookLogin(view)}'

Затем вы можете получить представление и получить контекст из представления в Viewmodel

3. Используйте AndroidViewModel

Класс AndroidViewModel совпадает с классом ViewModel, но не имеет контекста приложения.

Вы можете использовать контекст приложения с

gerApplication()

спасибо

Ответ 2

Есть несколько разных подходов к тому, как это сделать. Здесь я хочу поделиться своим подходом с вами. Что, на мой взгляд, является наиболее подходящим для шаблонной идеологии MVVM.

Как уже упоминалось, "Модель представления должна ничего не знать о представлении и ссылаться на него". Это оставляет не так много вариантов того, как View Model будет вызывать метод Activity. Во-первых, на ум приходит подход "слушатель". Но у этого подхода есть несколько недостатков на мой взгляд:

  • View должен позаботиться о подписке/отмене подписки на/из ViewModel, поскольку его время жизни, скорее всего, короче, чем ViewModel
  • Первый недостаток также приводит к ситуации, когда что-то происходит, и ViewModel должен вызывать метод View, но View находится между подпиской/отпиской; ViewModel также должен знать о ситуации с пустым слушателем, как это может быть null
  • При добавлении новых методов взаимодействия ViewModel-Activity вам нужно будет внести изменения в интерфейс ViewModel, Activity и 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 дважды.

Кроме того, нам нужно создать некоторый механизм, чтобы 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)
    }
}

Ничего сложного здесь. Просто MutableLiveData (отображается как LiveData) и метод для генерации событий. Кстати, внутри 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 } ?: [email protected]
            handleViewModelAction(event)
        })
    }

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

Как видите, общие события можно обрабатывать в BaseActivity, тогда как некоторые конкретные события можно обрабатывать путем переопределения метода handleViewModelAction.

Этот подход может быть изменен для конкретных нужд. Например, ViewModelEvent не должен работать с экземпляром Activity и может использоваться как событие "маркера" или может передавать некоторые конкретные параметры для требуемого действия и т.д.

Подход ViewModel Events делает связь ViewModel-Activity надежной и беспроблемной. Activity придется подписаться один раз, и он не пропустит последнее событие ViewModel.

Ответ 3

Хорошо, ваш подход неплохой. Но каким-то образом ваш интерфейс зависит от активности, если вы повторно используете свое представление, этот интерфейс не используется или может быть для этого сценария, вам нужно создать новый интерфейс для решения вашей проблемы.

Но если вы создадите экземпляр Activity, вы можете управлять им.