The correct way to obtain a ViewModel instance outside of an Activity or a Fragment

折月煮酒 提交于 2020-01-22 19:26:02

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!