Jak zaktualizować dane LiveData ViewModel z usługi w tle i zaktualizować interfejs użytkownika

97

Ostatnio odkrywam architekturę Androida, która została niedawno wprowadzona przez Google. Z dokumentacji znalazłem to:

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

działanie może uzyskać dostęp do tej listy w następujący sposób:

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

Moje pytanie brzmi: mam zamiar zrobić to:

  1. w loadUsers()funkcji pobieram dane asynchronicznie, gdzie najpierw sprawdzę bazę danych (pokój) pod kątem tych danych

  2. Jeśli nie otrzymam tam danych, wykonam wywołanie API w celu pobrania danych z serwera WWW.

  3. Wstawię pobrane dane do bazy danych (Pokój) i zaktualizuję interfejs użytkownika zgodnie z danymi.

Jakie jest zalecane podejście, aby to zrobić?

Jeśli zacznę Servicewywoływać interfejs API z loadUsers()metody, w jaki sposób mogę zaktualizować MutableLiveData<List<User>> userszmienną z tego Service?

CodeCameo
źródło
9
Przede wszystkim brakuje Ci repozytorium. Twój ViewModel nie powinien wykonywać żadnych zadań ładowania danych. Poza tym, ponieważ korzystasz z Room, Twoja Usługa nie musi bezpośrednio aktualizować LiveData w ViewModel. Usługa może polegać tylko na wstawianiu danych do pokoju, podczas gdy dane ViewModelData powinny być dołączane tylko do pokoju i pobierać aktualizacje z pokoju (po wstawieniu danych przez usługę). Aby uzyskać absolutnie najlepszą architekturę, spójrz na implementację klasy NetworkBoundResource u dołu tej strony: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić
dziękuję za sugestię :)
CodeCameo
1
Klasa repozytora nie jest wymieniona w oficjalnych dokumentach opisujących ROOM lub komponenty architektury Androida
Jonathan
2
Repozytorium to sugerowana najlepsza praktyka dotycząca separacji kodu i architektury, spójrz na ten przykład: codelabs.developers.google.com/codelabs/ ...
glisu
1
Funkcja w loadUsers()zasadzie wywoła repozytorium, aby uzyskać informacje o użytkowniku
CodeCameo

Odpowiedzi:

97

Zakładam, że używasz komponentów architektury Androida . W rzeczywistości nie ma znaczenia, dokąd dzwonisz, service, asynctask or handleraby zaktualizować dane. Możesz wstawić dane z usługi lub z asynctask przy użyciu postValue(..)metody. Twoja klasa wyglądałaby tak:

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

Jak usersjest LiveData, Roombaza danych jest odpowiedzialny za dostarczanie danych użytkowników, gdziekolwiek jest ona włożona.

Note: W architekturze podobnej do MVVM repozytorium jest głównie odpowiedzialne za sprawdzanie i pobieranie danych lokalnych i danych zdalnych.

androidcodehunter
źródło
3
Otrzymuję „java.lang.IllegalStateException: nie mogę uzyskać dostępu do bazy danych w głównym wątku, ponieważ” po wywołaniu moich metod db, jak powyżej. Czy możesz powiedzieć, co może być nie tak?
Prashant
Używam evernote-job, który aktualizuje bazę danych w tle, gdy jestem w interfejsie użytkownika. Ale LiveDatanie aktualizuje się
Akshay Chordiya
users.postValue(mUsers);-> Ale czy metoda postValue MutableLiveData jest w stanie zaakceptować LiveData ???
Cheok Yan Cheng
2
Mój błąd polegał na użyciu valuezamiast postValue. Dzięki za odpowiedź.
Hesam,
1
@pcj musisz albo wykonać operację pokoju w wątku, albo włączyć operacje w głównym wątku - Google, aby uzyskać więcej odpowiedzi.
kilokahn
46

Możesz użyć MutableLiveData<T>.postValue(T value)metody z wątku w tle.

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)
}
minhazur
źródło
19
Przypominamy, że postValue () jest chroniony w LiveData, ale publicznie dostępny w MutableLiveData
Long Ranger
ta praca nawet z zadania w tle iw sytuacjach, w których .setValue()nie byłoby to dozwolone
davejoem
18

... w funkcji loadUsers () pobieram dane asynchronicznie ... Jeśli uruchomię usługę w celu wywołania interfejsu API z metody loadUsers (), w jaki sposób mogę zaktualizować zmienną MutableLiveData> users z tej usługi?

Jeśli aplikacja pobiera dane użytkownika w wątku w tle, przydatna będzie funkcja postValue (zamiast setValue ).

W metodzie loadData znajduje się odniesienie do obiektu „users” MutableLiveData. Metoda loadData pobiera również świeże dane użytkownika skądś (na przykład z repozytorium).

Teraz, jeśli wykonanie odbywa się w wątku w tle, MutableLiveData.postValue () aktualizuje poza obserwatorami obiektu MutableLiveData.

Może coś takiego:

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

}
albert c braun
źródło
getUsers()Metoda repozytorium może wywołać interfejs API dla danych, które są asynchroniczne (może uruchomić usługę lub zadanie asynchroniczne) w takim przypadku w jaki sposób może zwrócić Listę z instrukcji return?
CodeCameo
Być może może przyjąć obiekt LiveData jako argument. (Coś w rodzaju repository.getUsers (użytkownicy)). Następnie metoda repozytorium wywoła samą user.postValue. W takim przypadku metoda loadUsers nie wymagałaby nawet wątku w tle.
Albert c braun
1
Dziękuję za odpowiedź ... jednak używam bazy danych pokoju do przechowywania, a DAO zwraca LiveData <Object> ... jak przekonwertować LiveData na MutableLiveData <Object>?
kilokahn,
Nie sądzę, aby DAO Room było tak naprawdę przeznaczone do obsługi obiektów MutableLiveData. DAO powiadamia Cię o zmianie w bazowej bazie danych, ale jeśli chcesz zmienić wartość w bazie danych, wywołałbyś metody DAO. Może też dyskusja tutaj się przyda: stackoverflow.com/questions/50943919/ ...
albert c braun
3

Zapoznaj się z przewodnikiem po architekturze systemu Android, który towarzyszy nowym modułom architektury, takim jak LiveDatai ViewModel. Dokładnie omawiają ten problem.

W swoich przykładach nie umieszczają tego w usłudze. Zobacz, jak rozwiązują ten problem za pomocą modułu „repozytorium” i Retrofit. Załączniki na dole zawierają bardziej kompletne przykłady, w tym informowanie o stanie sieci, raportowanie błędów itp.

user1978019
źródło
2

Jeśli dzwonisz do swojego api w repozytorium,

W repozytorium :

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

W ViewModel

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

W aktywności / fragmencie

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

Uwaga: Używając tutaj lambdy JAVA_1.8, możesz używać bez niej

Shubham Agrawal
źródło