Как завершить работу с ViewModel с помощью компонентов архитектуры Android?

Я пытаюсь выяснить, как наилучшим образом закончить Activity из ViewModel. Я нашел один способ сделать это, используя объект LiveData и испустив "сигнал".

У меня есть сомнения, что это решение имеет накладные расходы. Так это правильное решение, или я должен использовать более точные данные?

Итак, переходим к примеру: пусть предположим, что в приложении есть активность MainActivity и вид, как показано ниже:

 class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

        model.shouldCloseLiveData.observe(this, Observer { finish() })

    }
 }

и как компаньон для MainActivity - это MainViewModel, как показано ниже:

class MainViewModel(app: Application) : AndroidViewModel(app) {

  val shouldCloseLiveData = MutableLiveData<Void>()

  fun someAction(){
    shouldCloseLiveData.postValue(null)
  }

}

Ответ 1

Я разделяю ваши чувства, что это решение не выглядит аккуратным по двум причинам. Сначала использование объекта MutableLiveData для сигнала события является обходным путем. Данные не изменяются. Вторая экспозиция LiveData с внешней стороны модели представления нарушает принцип инкапсуляции в целом.

Я все еще удивляюсь этой уродливой концепции андроида. Они должны предоставить возможность наблюдать за моделью представления вместо внутренних LiveData объектов.

Я экспериментировал с WeakReference для реализации шаблона наблюдателя. Это было неустойчиво. Непредсказуемым образом был потерян референт WeakReference (null), в случаях которого нельзя было назвать finish(). Это было удивительно, поскольку я не думаю, что активность - это сбор мусора во время работы.

Так что это частично ответ на исключение. Образец наблюдения, реализованный как WeakReference, кажется, не является альтернативой вашему предложению.

Интересно, если законно реализовать шаблон наблюдателя с помощью жестких ссылок, если я удалю ссылки во время onStop() или onDestroy(). Я задал этот вопрос здесь.

Ответ 2

У меня была похожая проблема: у меня было два действия (A и B) с его моделями представления, связанными с объектом (таблицей в базе данных): от наблюдаемых живых данных мне пришлось перейти к другому действию B (от A до Б). Проблема заключалась в том, что после вызова нового действия B наблюдаемое в B изменило значение в наблюдаемом объекте. Действие A было все еще живым, его живые данные снова вызывают код навигации в B... в бесконечном цикле.

После некоторых исследований я понял, что запуск метода finish не означает, что действие действительно уничтожено.

Решение в наблюдаемом коде - удалить из реальных данных наблюдаемые, связанные с конкретной деятельностью.

liveData.removeObservers(activity);

Я показываю это в следующем фрагменте кода. Он написан на Java, но я думаю, у вас нет проблем с его чтением. С этим я решил свою проблему.

public class LockActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...    

        mViewModel = ViewModelProviders.of(this).get(ConfigurationViewModel.class);

        LiveData<Configurazione> liveData = mViewModel.getConfiguration();
        liveData.observe(this, config-> {
            // this code will be executed even another activity is in front of
            // screen
            boolean validToken = (config.getToken()!=null);

            if (!tokenValido) {
                intent = LoginActivity.createIntent(this);
            } else {
                intent = MainActivity.createIntent(this);
            }

            // this line remove the observable, so even activity will be destroied with calm, it is not a problem, the code is no more executed
            liveData.removeObservers(this);
        });
    }

    ...
}

Я думаю, что вы можете легко приспособиться к этой ситуации для вашего кода. Я надеюсь, что это помогает.

Ответ 3

Я согласен с тем, что, похоже, для этого нет хорошего решения, и ваше предложение работает довольно хорошо. Но я бы предложил следующее.

Поскольку вы используете Kotlin, вы можете передать функцию из вашей деятельности в viewmodel следующим образом:

ViewModel:

class MainViewModel(app: Application) : AndroidViewModel(app) {

    fun someAction(block: () -> Unit) {
        // do stuff
        block()
    }
}

Активность: здесь кнопка (и clicklistener) используется в качестве примера, но это может быть где угодно в коде активности.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

        myButton.setOnClickListener {
            model.someAction() {
                finish()
            }
        }
    }
}

функция block существу будет действовать как обратный вызов.