问题
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