在 Activity 或 Fragment 之外获取 ViewModel 实例的正确方法

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

我正在构建一个定位应用程序,在其中显示来自我的 MainActivity 中的 Room 数据库的背景位置。我可以通过调用

来获取 ViewModel
locationViewModel = ViewModelProviders.of(this).get(LocationViewModel.class);
locationViewModel.getLocations().observe(this, this);

当我通过 BroadCastReceiver 接收位置更新时,周期性的背景位置应该保存到 Room 数据库中。它们应该通过调用 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)
                }
            }
        }
    }
}

问题是我应该如何在非 activity class 中获取 LocationViewModel 实例,例如 BroadcastReceiver?调用 locationViewModel = ViewModelProviders.of(context).get(LocationViewModel.class) 是否正确,其中上下文是我从 BroadcastReceiver 的 onReceive (Context context, Intent intent) 接收到的上下文?

获得 ViewModel 后,是否需要使用 LiveData.observeForever and LiveData.removeObserver 因为 BroadcastReceiver 不是 LifecycleOwner?

传递 ViewModel 实例是一种反模式。

理想情况下,ViewModel 使用输入流(允许通过 BroasdcastReceiver 进行位置更新、用户操作等更新)和输出流(供 Activity/ 片段观察和显示到用户)

你的大致依赖创建应该是这样的,如果可以请使用像Dagger这样的DI框架:

主要Activity

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 与 RxJava 的 Subject 非常相似,非常适合这里的输入流。

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 流,而不是 RxJava 的 Observable。

这种方式数据真正以一种方式流动,符合 MVVM 架构

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?

没有。没用

您可以通过以下方式实现您想要的结果:

在单独的单例 class 中将您的 Room DB 操作与 ViewModel 分开。在 ViewModel 和任何其他需要的地方使用它。当接收到 Broadcast 时,通过这个单例 class 而不是 ViewModel 将数据写入 DB。

如果您正在观察片段中的 LiveData,那么它也会更新您的视图。