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


95

最近,我正在探索Google最近引入的Android体系结构。从文档中我发现:

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
        });
    }
}

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

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

  2. 如果没有收到数据,我将进行API调用以从Web服务器获取数据。

  3. 我将获取的数据插入数据库(Room)并根据数据更新UI。

推荐的方法是什么?

如果我开始ServiceloadUsers()方法中调用API,该如何从中更新MutableLiveData<List<User>> users变量Service


8
首先,您缺少存储库。您的ViewModel不应执行任何数据加载任务。除此之外,自您使用Room以来,您的服务不必直接在ViewModel中更新LiveData。Service只能将数据插入Room,而ViewModelData应该仅附加到Room,并从Room获取更新(在Service插入数据之后)。但是,要获得绝对最佳的体系结构,请从此页底部查看NetworkBoundResource类的实现:developer.android.com/topic/libraries/architecture/guide.html
MarkoGajić,

谢谢你的建议:)
CodeCameo '17

1
在描述ROOM或android体系结构组件的官方文档中未提及存储库类
Jonathan

2
建议使用存储库来实现代码分离和体系结构的最佳做法,请看以下示例:codelabs.developers.google.com/codelabs/…–
glisu

1
该函数loadUsers()基本上会调用该
仓库

Answers:


96

我假设您正在使用android体系结构组件。实际上,无论您在哪里调用service, asynctask or handler更新数据都没有关系。您可以使用postValue(..)方法从服务或从asynctask插入数据。您的课程如下所示:

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

users原样LiveDataRoom数据库负责将用户数据插入到任何地方。

Note: 在类似于MVVM的体系结构中,存储库主要负责检查和提取本地数据和远程数据。


3
我在调用上述db方法时收到“ java.lang.IllegalStateException:无法访问主线程上的数据库,因为它不能访问”,您能告诉我什么地方可能出错吗?
pcj

我正在使用evernote-job,当我在UI上时,它将在后台更新数据库。但LiveData没有更新
阿克沙伊Chordiya

users.postValue(mUsers);->但是,MutableLiveData的postValue方法是否可以接受LiveData?
Cheok Yan Cheng

2
我的错误是使用value而不是postValue。感谢您的回答。
Hesam '18年

1
@pcj,您需要在一个线程中进行房间操作或在主线程上启用操作-谷歌以获取更多答案。
kgkahn

46

您可以使用MutableLiveData<T>.postValue(T value)后台线程中的方法。

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

19
提醒一下,postValue()在LiveData中受保护,但在MutableLiveData中是公共的
Long Ranger

即使在后台任务中.setValue()也不允许执行此工作
davejoem

17

...在loadUsers()函数中,我正在异步获取数据...如果我启动一个服务以从loadUsers()方法调用API,如何从该Service更新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);        
        }
    });

}

getUsers()在这种情况下,存储库的方法可能会为异步数据调用api(可能为此启动服务或asynctask),如何从返回语句返回List?
CodeCameo

也许可以将LiveData对象作为参数。(类似于repository.getUsers(users))。然后,存储库方法将调用users.postValue本身。而且,在这种情况下,loadUsers方法甚至不需要后台线程。
albert c braun

1
谢谢您的回答。但是,我正在使用Room DB进行存储,并且DAO返回了LiveData <Object> ...如何将LiveData转换为MutableLiveData <Object>?
kgkahn

我认为Room的DAO并不是真的打算处理MutableLiveData对象。DAO会通知您对基础数据库的更改,但是,如果您想更改数据库中的值,则可以调用DAO的方法。此外,也许这里的讨论是有益的:stackoverflow.com/questions/50943919/...
阿尔伯特ç布朗

3

看看新的架构模块(如和)随附的Android架构指南LiveDataViewModel。他们深入讨论了这个确切的问题。

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


2

如果您要在存储库中调用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,无需使用即可使用

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.