Правильный способ получить экземпляр ViewModel вне Activity или Fragment

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

locationViewModel = ViewModelProviders.of(this).get(LocationViewModel.class);
locationViewModel.getLocations().observe(this, this);

Периодические фоновые местоположения должны сохраняться в базе данных Room, когда я получаю обновления местоположения через BroadCastReceiver. Их следует сохранить, вызвав locationViewModel.getLocations().setValue()

public class LocationUpdatesBroadcastReceiver extends BroadcastReceiver {

    static final String ACTION_PROCESS_UPDATES =
            "com.google.android.gms.location.sample.backgroundlocationupdates.action" +
                    ".PROCESS_UPDATES";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_PROCESS_UPDATES.equals(action)) {
                LocationResult result = LocationResult.extractResult(intent);
                if (result != null) {
                    List<Location> locations = result.getLocations();
                    List<SavedLocation> locationsToSave = covertToSavedLocations(locations)
                    //Need an instance of LocationViewModel to call locationViewModel.getLocations().setValue(locationsToSave)
                }
            }
        }
    }
}

Вопрос в том, как мне получить экземпляр LocationViewModel в классе без активности, подобном этому BroadcastReceiver? Правильно ли вызывать locationViewModel = ViewModelProviders.of(context).get(LocationViewModel.class), где контекст — это контекст, который я получаю от onReceive (Context context, Intent intent) BroadcastReceiver?

После получения ViewModel мне нужно использовать LiveData.observeForever и LiveData .removeObserver, так как BroadcastReceiver не является LifecycleOwner?


person PrashanD    schedule 24.06.2018    source источник


Ответы (2)


Вопрос в том, как мне получить экземпляр LocationViewModel в классе без активности, подобном этому BroadcastReceiver?

Вы не должны этого делать. Это плохая практика проектирования.

Правильно ли вызывать locationViewModel = ViewModelProviders.of(context).get(LocationViewModel.class), где контекст — это контекст, который я получаю от onReceive (контекстный контекст, намерение) BroadcastReceiver?

Нет. Это не поможет.

Вы можете добиться желаемого результата следующим образом:

Отделите свою операцию Room DB от ViewModel в отдельном одноэлементном классе. Используйте его в ViewModel и любом другом необходимом месте. Когда широковещательная рассылка получена, записывайте данные в БД через этот одноэлементный класс, а не через ViewModel.

Если вы наблюдаете за LiveData в своем фрагменте, он также обновит ваши представления.

person Sagar    schedule 24.06.2018
comment
Да, вот что я подумал. Уже есть синглтон БД. AppDatabase.savedLocationDao().saveLocations. Но было бы красивее, если бы я мог просто использовать locationViewModel.getLocations() типа MutableLiveData<List<SavedLocations>> для обновления местоположений. - person PrashanD; 24.06.2018
comment
@PrashanD На основе документации ViewModel всегда создается в связи с областью действия (фрагментом или действием) и будет сохраняться до тех пор, пока область действия жива. Вы не можете добиться этого с ViewModel. - person Sagar; 24.06.2018

Это анти-шаблон для передачи экземпляра ViewModel.

В идеале ViewModel настраивается один раз с входным потоком (позволяющим обновления, такие как обновления местоположения через BroadcastReceiver, действия пользователя и т. д.) и выходным потоком (для наблюдения и отображения действия/фрагмента пользователям)

Ваше грубое создание зависимости должно быть следующим, пожалуйста, используйте DI-фреймворк, такой как Dagger, если можете:

Основное действие

protected void onCreate(Bundle savedBundleState) {
    PublishSubject<List<SavedLocation>> currentLocationSubject = new PublishSubject();// here I'm using RxJava but you can use alternatives
    MyBroastcastReceiver broadcastReceiver = new MyBroadcastReceiver(currentLocationSubject);
    locationViewModel = ViewModelProviders.of(this).get(LocationViewModel.class);
    locationViewModel.setLocationInputStream(currentLocationSubject);
    locationViewModel.init();
}

MutableLiveData очень похож на Subject RxJava, который идеально подходит для входного потока.

Мой широковещательный приемник

static final String ACTION_PROCESS_UPDATES =
            "com.google.android.gms.location.sample.backgroundlocationupdates.action" +
                    ".PROCESS_UPDATES";

    private PublishSubject<List<SavedLocation>> currentLocationSubject = new PublishSubject();

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_PROCESS_UPDATES.equals(action)) {
                LocationResult result = LocationResult.extractResult(intent);
                if (result != null) {
                    List<Location> locations = result.getLocations();
                    List<SavedLocation> locationsToSave = covertToSavedLocations(locations)
                    currentLocationSubject.onNext(locationsToSave);// add input to the input stream
                }
            }
        }
    }

Модель представления местоположения

Observable<List<SavedLocation>> inputLocationStream;
PublishSubject<List<SavedLocation>> outputLocationStream = new PublishSubject();

void setLocationInputStream(Observable<List<SavedLocation>> inputLocationStream) {
    this.inputLocationStream = inputLocationStream;
}

void init() {
    inputLocationStream.subscribe {
        saveLocationList -> {
            outputLocationStream.onNext(saveLocationList);
        }
    }
}

Теперь вы можете просто наблюдать за выходным потоком, который может быть потоком LiveData вместо Observable RxJava.

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

person ericn    schedule 24.06.2018
comment
Я не пытаюсь передать ViewModel. ViewModelProviders.of(this).get(ABC.class); — это задокументированный способ получения ViewModel для области ABC.class, где ABC — это Activity или Fragment. Передача locationViewModel.getLocations() в BroadCastReceiver была бы еще большим антишаблоном. - person PrashanD; 24.06.2018
comment
@ericn означает, нужны ли мне два объекта, один из которых - rxjava publishsubject и livedata для изменения активности? - person Abhay Koradiya; 27.02.2020