Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 671
PrashanD Avatar asked Jun 24 '18 05:06

PrashanD


People also ask

How do I get ViewModel instance in fragment?

Use activity's scope to create an instance of the ViewModel in fragment so that all the fragments and activity now has a common ViewModel and have access to the same ViewModelStore . If the activity is re-created, it and all its fragments receive the same ViewModel instance that was created by the associated activity.

Can a ViewModel be shared between activity and fragment?

Use the ViewModel to update the UI You will use the activity instance instead of the fragment instance, and you will see how to do this in the coming sections. That means the view model can be shared across fragments.

Who creates the instance of a ViewModel?

As the library is the one responsible for creating the ViewModel, we do not get to call its constructor, the library is doing that internally, and by default it will always call the empty constructor, making it impossible to pass data to it.


2 Answers

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.

like image 84
Sagar Avatar answered Oct 09 '22 17:10

Sagar


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

like image 29
ericn Avatar answered Oct 09 '22 16:10

ericn