如何从后台服务更新 ViewModel 的 LiveData 并更新 UI

Posted

技术标签:

【中文标题】如何从后台服务更新 ViewModel 的 LiveData 并更新 UI【英文标题】:How to update LiveData of a ViewModel from background service and Update UI 【发布时间】:2017-10-27 13:09:56 【问题描述】:

最近我正在探索谷歌最近推出的 android 架构。从Documentation 我发现了这个:

public class MyViewModel extends ViewModel 
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() 
        if (users == null) 
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        
        return users;
    

    private void loadUsers() 
        // do async operation to fetch users
    

活动可以按如下方式访问此列表:

public class MyActivity extends AppCompatActivity 
    public void onCreate(Bundle savedInstanceState) 
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> 
            // update UI
        );
    

我的问题是,我要这样做:

    loadUsers() 函数中,我异步获取数据,我将首先检查数据库(房间)中的数据

    如果我没有在那里获取数据,我将调用 API 从 Web 服务器获取数据。

    我会将获取的数据插入数据库(房间)并根据数据更新 UI。

推荐的方法是什么?

如果我启动 Service 以从 loadUsers() 方法调用 API,我如何从 Service 更新 MutableLiveData&lt;List&lt;User&gt;&gt; users 变量?

【问题讨论】:

首先,您缺少一个存储库。您的 ViewModel 不应执行任何数据加载任务。除此之外,由于您使用 Room,您的服务不必直接更新 ViewModel 中的 LiveData。 Service 只能将数据插入 Room,而您的 ViewModelData 应该只附加到 Room,并从 Room 获取更新(在 Service 插入数据之后)。但是对于绝对最佳的架构,请查看本页底部的 NetworkBoundResource 类实现:developer.android.com/topic/libraries/architecture/guide.html 谢谢你的建议:) Repositor 类在描述 ROOM 或 android 架构组件的官方文档中未提及 Repository 是代码分离和架构的建议最佳实践,看这个例子:codelabs.developers.google.com/codelabs/… 函数loadUsers()基本上会调用repo获取用户信息 【参考方案1】:

我假设您使用的是android architecture components。实际上,无论您在哪里调用service, asynctask or handler 来更新数据都无关紧要。您可以使用postValue(..) 方法从服务或异步任务中插入数据。你的班级应该是这样的:

private void loadUsers() 
    // do async operation to fetch users and use postValue() method
    users.postValue(listOfData)

由于usersLiveData,所以Room 数据库负责为用户提供任何插入的数据。

Note: 在类似 MVVM 的架构中,repository 主要负责检查和拉取本地数据和远程数据。

【讨论】:

我得到“java.lang.IllegalStateException:无法访问主线程上的数据库,因为它”在调用我的数据库方法时,你能告诉我可能出了什么问题吗? 我正在使用 evernote-job,它在我使用 UI 时在后台更新数据库。但是LiveData 没有更新 users.postValue(mUsers); -> 但是,MutableLiveData 的 postValue 方法能接受 LiveData 吗??? 我的错误是使用value 而不是postValue。谢谢你的回答。 @pcj 您需要在线程中执行房间操作或在主线程上启用操作 - 谷歌以获得更多答案。【参考方案2】:

您可以在后台线程中使用MutableLiveData&lt;T&gt;.postValue(T value) 方法。

private void loadUsers() 
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)

【讨论】:

提醒一下,postValue() 在 LiveData 中是受保护的,但在 MutableLiveData 中是公开的 即使在后台任务中以及在不允许.setValue() 的情况下也能工作【参考方案3】:

... 在 loadUsers() 函数中,我正在异步获取数据... 如果我启动服务以从 loadUsers() 方法调用 API,如何 我可以从中更新 MutableLiveData> users 变量吗 服务?

如果应用在后台线程上获取用户数据,postValue(而不是setValue)将很有用。

在 loadData 方法中有一个对 MutableLiveData“用户”对象的引用。 loadData 方法还从某个地方(例如,存储库)获取一些新的用户数据。

现在,如果在后台线程上执行,则 MutableLiveData.postValue() 会更新 MutableLiveData 对象的外部观察者。

可能是这样的:

private MutableLiveData<List<User>> users;

.
.
.

private void loadUsers() 
    // do async operation to fetch users
    ExecutorService service =  Executors.newSingleThreadExecutor();
    service.submit(new Runnable() 
        @Override
        public void run() 
            // on background thread, obtain a fresh list of users
            List<String> freshUserList = aRepositorySomewhere.getUsers();

            // now that you have the fresh user data in freshUserList, 
            // make it available to outside observers of the "users" 
            // MutableLiveData object
            users.postValue(freshUserList);        
        
    );


【讨论】:

Repository 的getUsers() 方法可能会为异步数据调用 api(可能为此启动服务或异步任务),在这种情况下,它如何从它的 return 语句中返回 List? 也许它可以将 LiveData 对象作为参数。 (类似于 repository.getUsers(users))。然后存储库方法将调用 users.postValue 本身。而且,在这种情况下,loadUsers 方法甚至不需要后台线程。 感谢您的回答 .... 但是,我使用 Room DB 进行存储,DAO 返回 LiveData ...如何将 LiveData 转换为 MutableLiveData? 我不认为 Room 的 DAO 真的打算处理 MutableLiveData 对象。 DAO 会通知您底层数据库的更改,但是,如果您想更改数据库中的值,您将调用 DAO 的方法。也可能这里的讨论很有用:***.com/questions/50943919/…【参考方案4】:

看看Android architecture guide 与LiveDataViewModel 等新架构模块相伴的。他们深入讨论了这个确切的问题。

在他们的示例中,他们没有将其放入服务中。看看他们如何使用“存储库”模块和改造来解决它。底部的附录包括更完整的示例,包括通信网络状态、报告错误等。

【讨论】:

【参考方案5】:

如果你在 Repository 中调用你的 api,那么,

存储库中:

public MutableLiveData<LoginResponseModel> checkLogin(LoginRequestModel loginRequestModel) 
    final MutableLiveData<LoginResponseModel> data = new MutableLiveData<>();
    apiService.checkLogin(loginRequestModel)
            .enqueue(new Callback<LoginResponseModel>() 
                @Override
                public void onResponse(@NonNull Call<LoginResponseModel> call, @Nullable Response<LoginResponseModel> response) 
                    if (response != null && response.isSuccessful()) 
                        data.postValue(response.body());
                        Log.i("Response ", response.body().getMessage());
                    
                

                @Override
                public void onFailure(@NonNull Call<LoginResponseModel> call, Throwable t) 
                    data.postValue(null);
                
            );
    return data;

ViewModel

public LiveData<LoginResponseModel> getUser() 
    loginResponseModelMutableLiveData = repository.checkLogin(loginRequestModel);
    return loginResponseModelMutableLiveData;

活动/片段

loginViewModel.getUser().observe(LoginActivity.this, loginResponseModel -> 
        if (loginResponseModel != null) 
            Toast.makeText(LoginActivity.this, loginResponseModel.getUser().getType(), Toast.LENGTH_SHORT).show();
        
    );

注意:这里使用 JAVA_1.8 lambda,你可以不使用它

【讨论】:

以上是关于如何从后台服务更新 ViewModel 的 LiveData 并更新 UI的主要内容,如果未能解决你的问题,请参考以下文章

如何从不同的 ViewModel 更新文本块和进度条?

如何从后台服务android更新谷歌地图v2位置

MVVM:修改模型,如何正确更新 ViewModel 和 View?

如何从服务中访问我的活动的 ViewModel?

如何在android + viewModel中进行回调

从事件更新 Viewmodel - PropertyChangedEventHandler 为空?