Правильный способ получить экземпляр 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), где context - это контекст, который я получаю от onReceive (Context context, Intent intent) BroadcastReceiver?

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

12
0
7 452
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

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

Примерное создание зависимости должно выглядеть следующим образом, пожалуйста, используйте платформу 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, который здесь идеально подходит для входного потока.

MyBroastcastReceiver

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
                }
            }
        }
    }

LocationViewModel

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.

Я не пытаюсь передать ViewModel. ViewModelProviders.of(this).get(ABC.class); - это документированный способ получения ViewModel для области ABC.class, где ABC - это действие или фрагмент. Передача locationViewModel.getLocations() в BroadCastReceiver была бы еще большим анти-шаблоном.

PrashanD 24.06.2018 08:03

@ericn означает, что мне нужны два объекта, один - это rxjava publishsubject и liveata для изменения активности?

Abhay Koradiya 27.02.2020 06:02
Ответ принят как подходящий

Question is how should I get the LocationViewModel instance in a non-activity class like this BroadcastReceiver?

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

Is it correct to call locationViewModel = ViewModelProviders.of(context).get(LocationViewModel.class) where context is the context that I receive from onReceive (Context context, Intent intent) of the BroadcastReceiver?

Нет, это не поможет

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

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

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

Да, я так и думал. Уже есть синглтон БД. AppDatabase.savedLocationDao().saveLocations. Но было бы лучше, если бы я мог просто использовать locationViewModel.getLocations() типа MutableLiveData<List<SavedLocations>> для обновления локаций.

PrashanD 24.06.2018 08:08

@PrashanD На основе documentaiton ViewModel всегда создается в связи с областью действия (фрагментом или действием) и будет сохраняться, пока существует область действия. Вы не можете добиться этого с помощью ViewModel.

Sagar 24.06.2018 08:12

Другие вопросы по теме