Як оновити LiveData ViewModel із фонової служби та оновлення інтерфейсу користувача


95

Нещодавно я досліджую архітектуру 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. Я вставлю отримані дані в базу даних (Кімната) та оновлюю інтерфейс відповідно до даних.

Який рекомендований підхід для цього?

Якщо я починаю Serviceвикликати API із loadUsers()методу, як я можу оновити MutableLiveData<List<User>> usersзмінну з цього Service?


8
Перш за все, вам не вистачає сховища. Ваш ViewModel не повинен виконувати жодних завдань із завантаження даних. Крім цього, з моменту використання Кімнати Ваша Служба не повинна безпосередньо оновлювати LiveData у ViewModel. Служба може лише вставляти дані до кімнати, тоді як ваші ViewModelData слід приєднувати лише до кімнати, а отримувати оновлення від кімнати (після того, як служба вставляє дані). Але для абсолютної найкращої архітектури подивіться на реалізацію класу NetworkBoundResource внизу цієї сторінки: developer.android.com/topic/libraries/architecture/guide.html
Марко Гаїч

дякую за пропозицію :)
CodeCameo

1
Клас Repositor не згадується в офіційних документах, що описують ROOM або компоненти архітектури Android
Джонатан

2
Сховище - це рекомендована найкраща практика для розділення коду та архітектури, подивіться на цей приклад: codelabs.developers.google.com/codelabs/…
glisu

1
Функція в loadUsers()основному викликає репо для отримання інформації про користувача
CodeCameo

Відповіді:


96

Я припускаю, що ви використовуєте компоненти архітектури 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, сховище в основному відповідає за перевірку та витягування локальних даних та віддалених даних.


3
Я отримую "java.lang.IllegalStateException: Не вдається отримати доступ до бази даних в основному потоці, оскільки він" при виклику моїх методів db, як вище, чи можете ви сказати, що може бути не так?
pcj

Я використовую evernote-job, який оновлює базу даних у фоновому режимі, поки я перебуваю в інтерфейсі користувача. Але LiveDataне оновлюється
Акшай Чордія

users.postValue(mUsers);-> Але, чи здатний метод postValue MutableLiveData приймати LiveData ???
Cheok Yan Cheng

2
Моєю помилкою було використання valueзамість postValue. Дякую за вашу відповідь.
Hesam

1
@pcj вам потрібно або виконати кімнатну операцію в потоці, або увімкнути операції над основною ланцюжком - google, щоб отримати більше відповідей.
кілокан

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 () я отримую дані асинхронно ... Якщо я запускаю службу для виклику 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

Можливо, це може сприйняти об'єкт LiveData як аргумент. (Щось на зразок repository.getUsers (користувачі)). Тоді метод сховища викликав би сам user.postValue. І, в такому випадку, методу loadUsers навіть не потрібен фоновий потік.
Альберт Браун

1
Дякую за відповідь .... однак, я використовую БД кімнати для зберігання, а DAO повертає LiveData <Object> ... як мені перетворити LiveData на MutableLiveData <Object>?
кілокан

Я не думаю, що DAO Room дійсно призначений для роботи з об'єктами MutableLiveData. DAO повідомляє вас про зміну базової бази даних, але, якщо ви хочете змінити значення в БД, ви можете викликати методи DAO. Також, можливо, обговорення тут є корисним: stackoverflow.com/questions/50943919/…
albert c braun

3

Погляньте на посібник з архітектури Android, який супроводжує нові архітектурні модулі, такі як LiveDataі ViewModel. Вони поглиблено обговорюють саме це питання.

У своїх прикладах вони не ставлять це в службу. Подивіться, як вони вирішують це за допомогою модуля "сховища" та модернізації. Додатки внизу містять більш повні приклади, включаючи зв’язок про стан мережі, повідомлення про помилки тощо.


2

Якщо ви викликаєте свій api в Repository,

У сховищі :

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, ви можете використовувати її без неї

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.