Android LiveData - как повторно использовать одну и ту же ViewModel для разных действий?

Пример ViewModel:

public class NameViewModel extends ViewModel {
    // Create a LiveData with a String
    private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<>();
        }
        return mCurrentName;
    }

}

Основная деятельность:

mModel = ViewModelProviders.of(this).get(NameViewModel.class);

// Create the observer which updates the UI.
final Observer<String> nameObserver = textView::setText;

// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getCurrentName().observe(this, nameObserver);

Я хочу вызвать mModel.getCurrentName().setValue(anotherName); во втором действии и заставить MainActivity получать изменения. Это возможно?


person user1209216    schedule 19.03.2018    source источник
comment
Правильный ответ заключается в том, что если вы хотите обмениваться данными между ними, они не должны быть разными Activity, и вместо этого вы должны обменивать фрагменты.   -  person EpicPandaForce    schedule 19.03.2018
comment
Возможно, @EpicPandaForce, но не так работает ни шаблон master / detail AndroidStudio, ни чертежи архитектуры Android.   -  person Mark    schedule 10.07.2018
comment
@Mark, значит, это недостаток архитектурных чертежей Android и шаблона.   -  person EpicPandaForce    schedule 26.01.2019
comment
См. stackoverflow.com/questions/56521969/   -  person Levon Petrosyan    schedule 10.06.2019


Ответы (3)


Когда вы вызываете ViewModelProviders.of(this), вы фактически создаете / сохраняете ViewModelStore, который привязан к this, поэтому разные Activity имеют разные ViewModelStore, и каждый ViewModelStore создает другой экземпляр ViewModel с использованием данной фабрики, поэтому у вас не может быть одного и того же экземпляра ViewModel в разных ViewModelStoreс.

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

Например:

public class SingletonNameViewModelFactory extends ViewModelProvider.NewInstanceFactory {


    NameViewModel t;

    public SingletonNameViewModelFactory() {
      //  t = provideNameViewModelSomeHowUsingDependencyInjection
    }

    @Override
    public NameViewModel create(Class<NameViewModel> modelClass) {
        return t;
    }
}

Итак, вам нужно сделать SingletonNameViewModelFactory синглтон (например, с помощью Dagger) и использовать его следующим образом:

mModel = ViewModelProviders.of(this,myFactory).get(NameViewModel.class);

Примечание.

Сохранение ViewModels среди разных областей видимости - это антипаттерн. Настоятельно рекомендуется сохранить объекты уровня данных (например, сделать источник данных или репозиторий одноэлементным) и сохранить данные между различными областями (действиями).

Подробнее читайте в этой статье.

person Saeed Masoumi    schedule 19.03.2018
comment
Если вы уже кэшируете данные в одноэлементном слое данных, тогда в чем смысл ViewModel? - person EpicPandaForce; 19.03.2018
comment
@EpicPandaForce, я имею в виду, что независимо от того, как действует ваш уровень данных, ViewModel должен каким-то образом уведомлять об изменениях ваших данных. Таким образом, кэширование вашего уровня данных - это один из способов поддерживать ваши данные в разных областях. - person Saeed Masoumi; 19.03.2018
comment
Что ж, да, я думаю, что вы правы, я не должен пытаться это делать. Но как насчет фрагмента, это хорошая практика, чтобы дочерние фрагменты соблюдали одну и ту же модель представления? Например ViewModelProviders.of(getActivity()).get(NameViewModel.class) внутри фрагмента. - person user1209216; 20.03.2018
comment
@ user1209216 Да, почему бы не просмотреть подробную информацию в этой статье developer.android.com / topic / библиотеки / архитектура / - person Saeed Masoumi; 20.03.2018
comment
Как этого добиться, если использовать DaggerViewModelFactory? - person Morteza Rastgoo; 17.08.2019

При получении модели представления с помощью ViewModelProvider, который вы передаете как владелец жизненного цикла MainActivity, это даст модель представления для этого действия. Во втором действии вы получите другой экземпляр этой модели представления, на этот раз для второго действия. Вторая модель будет иметь вторые живые данные.

Что вы можете сделать, так это поддерживать данные на другом уровне, например в репозитории, который может быть синглтоном, и таким образом вы можете использовать ту же модель представления.

введите описание изображения здесь

public class NameViewModel extends ViewModel {
    // Create a LiveData with a String
    private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = DataRepository.getInstance().getCurrentName();
        }
        return mCurrentName;
    }
}

//SingleTon
public class DataRepository     

    private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<>();
        }
        return mCurrentName;
    }
//Singleton code
...
}
person TotoliciCristian    schedule 19.03.2018
comment
это должен быть принятый ответ .. без нарушения шаблона архитектуры Android - person sum20156; 29.12.2020
comment
Этот подход рекомендуется также здесь в этом официальном руководстве: LiveData in repositories: To avoid leaking ViewModels and callback hell, repositories can be observed - person alierdogan7; 17.06.2021

Просто создайте экземпляр своей ViewModel, в данном случае NameViewModel.

Ваша фабрика ViewModel будет похожа на

class ViewModelFactory : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>) =
        with(modelClass){
            when {
                isAssignableFrom(NameViewModel::class.java) -> NameViewModel.getInstance()
                else -> throw IllegalArgumentException("Unknown viewModel class $modelClass")
            }
        } as T


    companion object {
        private var instance : ViewModelFactory? = null
        fun getInstance() =
            instance ?: synchronized(ViewModelFactory::class.java){
                instance ?: ViewModelFactory().also { instance = it }
            }
    }
}

И ваша ViewModel

class NameViewModel : ViewModel() {

    //your liveData objects and many more...

    companion object {
        private var instance : NameViewModel? = null
        fun getInstance() =
            instance ?: synchronized(NameViewModel::class.java){
                instance ?: NameViewModel().also { instance = it }
            }
    }
}

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

ViewModelProviders.of(this, ViewModelFactory.getInstance()).get(NameViewModel::class.java)

ИЛИ

создать функцию расширения для облегчения доступа

fun <T : ViewModel> AppCompatActivity.getViewModel(viewModelClass: Class<T>) =
    ViewModelProviders.of(this, ViewModelFactory.getInstance()).get(viewModelClass)
person Amir Raza    schedule 25.11.2019
comment
Я пробовал это решение, но оно не работает. Попытка обновить TextView во фрагменте из второго действия. - person Samuel; 23.01.2020
comment
Можете ли вы взглянуть на мою заявку: stackoverflow.com/questions/59887014/ - person Samuel; 24.01.2020
comment
Работает как шарм! Спасибо - person Captain Allergy; 12.06.2021