问题
I'm building a location app where I display background locations from a Room database in my MainActivity. I can get a ViewModel by calling
locationViewModel = ViewModelProviders.of(this).get(LocationViewModel.class);
locationViewModel.getLocations().observe(this, this);
Periodic background locations should be saved to the Room database when I receive location updates via a BroadCastReceiver. They should be saved by calling 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)
}
}
}
}
}
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?
After getting the ViewModel, do I need to use LiveData.observeForever and LiveData.removeObserver since the BroadcastReceiver is not a LifecycleOwner?
回答1:
Question is how should I get the LocationViewModel instance in a non-activity class like this BroadcastReceiver?
You shouldn't do that. Its bad design practice.
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?
No. It won't help
You can achieve your desired outcome as follows:
Separate your Room DB operation from ViewModel
in a separate singleton class. Use it in ViewModel
and any other place required. When Broadcast is received, write data to DB through this singleton class rather than ViewModel
.
If you are observing for the LiveData
in your Fragment, then it will update your views too.
回答2:
It's an anti-pattern to pass the ViewModel instance around.
Ideally, the ViewModel is set up once with input stream (allowing updates like location updates thru a BroasdcastReceiver, user's actions etc.) and an output stream (for the Activity/ Fragment to observe and display to users)
Your rough dependency creation should be as follows, please us DI framework like Dagger if you can:
MainActivity
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 is quite similar to RxJava's Subject which is perfect for the input stream here.
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);
}
}
}
Now you can just observe the output stream which can be a LiveData stream instead of RxJava's Observable.
This way data really flows 1 way, compliant with MVVM architecture
来源:https://stackoverflow.com/questions/51007271/the-correct-way-to-obtain-a-viewmodel-instance-outside-of-an-activity-or-a-fragm