Як оновити 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.