Я создаю приложение для определения местоположения, в котором я отображаю фоновые местоположения из базы данных 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?
Передача экземпляра 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.
@ericn означает, что мне нужны два объекта, один - это rxjava publishsubject и liveata для изменения активности?
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 На основе documentaiton ViewModel всегда создается в связи с областью действия (фрагментом или действием) и будет сохраняться, пока существует область действия. Вы не можете добиться этого с помощью ViewModel.
Я не пытаюсь передать ViewModel.
ViewModelProviders.of(this).get(ABC.class);- это документированный способ получения ViewModel для области ABC.class, где ABC - это действие или фрагмент. ПередачаlocationViewModel.getLocations()в BroadCastReceiver была бы еще большим анти-шаблоном.