Как обновить LiveData ViewModel из фоновой службы и обновления пользовательского интерфейса

97

Недавно я изучаю архитектуру Android, которую недавно представил Google. Из документации я нашел это:

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 для получения данных с веб-сервера.

  3. Я вставлю полученные данные в базу данных (Room) и обновлю пользовательский интерфейс в соответствии с данными.

Какой рекомендуемый подход для этого?

Если я начну Serviceвызывать API из loadUsers()метода, как я могу обновить MutableLiveData<List<User>> usersпеременную из этого Service?

CodeCameo
источник
9
Прежде всего, вам не хватает репозитория. Ваша ViewModel не должна выполнять никаких задач по загрузке данных. Кроме того, поскольку вы используете Room, ваша служба не должна обновлять LiveData напрямую в ViewModel. Служба может только вставлять данные в комнату, в то время как ваши ViewModelData должны быть прикреплены только к комнате и получать обновления из комнаты (после того, как служба вставляет данные). Но для получения наилучшей архитектуры посмотрите на реализацию класса
Марко Гайич
спасибо за предложение :)
CodeCameo
1
Класс Repositor не упоминается в официальных документах, описывающих ROOM или компоненты архитектуры Android
Джонатан
2
Репозиторий - это рекомендуемый лучший метод
glisu
1
Функция в loadUsers()основном вызывает репо для получения информации о пользователе
CodeCameo

Ответы:

97

Я предполагаю, что вы используете компоненты архитектуры Android . На самом деле не имеет значения, куда вы звоните, service, asynctask or handlerчтобы обновить данные. Вы можете вставить данные из службы или из асинхронной задачи с помощью postValue(..)метода. Ваш класс будет выглядеть так:

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

Как usersэто LiveData, Roomбаза данных отвечает за предоставление данных пользователей , где она установлена.

Note: В архитектуре, подобной MVVM, репозиторий в основном отвечает за проверку и извлечение локальных и удаленных данных.

androidcodehunter
источник
3
Я получаю «java.lang.IllegalStateException: Невозможно получить доступ к базе данных в основном потоке, так как он» при вызове моих методов базы данных, как указано выше. Можете ли вы сказать, что может быть не так?
Prashant
Я использую evernote-job, который обновляет базу данных в фоновом режиме, пока я нахожусь в пользовательском интерфейсе. Но LiveDataне обновляется
Акшай Чордия
users.postValue(mUsers);-> Но может ли метод postValue MutableLiveData принимать LiveData ???
Cheok Yan Cheng
2
Моя ошибка заключалась в использовании valueвместо postValue. Спасибо за Ваш ответ.
Hesam
1
@pcj вам нужно либо выполнить операцию с комнатой в потоке, либо включить операции в основном потоке - дополнительные ответы можно найти в Google.
kilokahn
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
18

... в функции loadUsers () я получаю данные асинхронно ... Если я запускаю службу для вызова API из метода loadUsers (), как я могу обновить переменную 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 для данных, которые являются асинхронными (для этого может быть запущена служба или асинхронная задача). В этом случае как он может вернуть список из своего оператора возврата?
CodeCameo 09
Возможно, он мог бы принять в качестве аргумента объект LiveData. (Что-то вроде repository.getUsers (users)). Тогда метод репозитория сам вызовет users.postValue. И в этом случае методу loadUsers даже не потребуется фоновый поток.
albert c braun
1
Спасибо за ответ .... однако я использую базу данных комнаты для хранения, а DAO возвращает LiveData <Object> ... как мне преобразовать LiveData в MutableLiveData <Object>?
kilokahn
Я не думаю, что DAO Room действительно предназначена для работы с объектами MutableLiveData. DAO уведомляет вас об изменении базового db, но, если вы хотите изменить значение в DB, ​​вы должны вызвать методы DAO. Также, возможно, обсуждение здесь будет полезным: stackoverflow.com/questions/50943919/…
albert c braun
3

Ознакомьтесь с руководством по архитектуре Android, которое прилагается к модулям новой архитектуры, таким как LiveDataи ViewModel. Они подробно обсуждают именно этот вопрос.

В своих примерах они не помещают его в сервис. Посмотрите, как они решают эту проблему с помощью модуля «репозиторий» и дооснащения. Дополнения внизу включают более полные примеры, в том числе сообщения о состоянии сети, сообщения об ошибках и т. Д.

user1978019
источник
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, вы можете использовать без нее

Шубхам Агравал
источник