Come aggiornare LiveData di un ViewModel dal servizio in background e dall'UI di aggiornamento


95

Recentemente sto esplorando l'architettura Android, che è stata introdotta di recente da Google. Dalla documentazione ho trovato questo:

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

l'attività può accedere a questo elenco come segue:

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

La mia domanda è, farò questo:

  1. nella loadUsers()funzione sto recuperando i dati in modo asincrono dove controllerò prima il database (Room) per quei dati

  2. Se non ottengo i dati lì, effettuerò una chiamata API per recuperare i dati dal server web.

  3. Inserirò i dati recuperati nel database (Stanza) e aggiornerò l'interfaccia utente in base ai dati.

Qual è l'approccio consigliato per farlo?

Se avvio una Serviceper chiamare l'API dal loadUsers()metodo, come posso aggiornare la MutableLiveData<List<User>> usersvariabile da quella Service?


8
Prima di tutto, ti manca un repository. Il tuo ViewModel non dovrebbe eseguire alcuna attività di caricamento dei dati. Oltre a questo, dal momento che stai utilizzando Room, il tuo servizio non deve aggiornare direttamente i LiveData nel ViewModel. Il servizio può solo inserire dati in Room, mentre ViewModelData deve essere allegato solo a Room e ricevere aggiornamenti da Room (dopo che il servizio inserisce i dati). Ma per la migliore architettura in assoluto, guarda l'implementazione della classe NetworkBoundResource dal fondo di questa pagina: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić

grazie per il suggerimento :)
CodeCameo

1
La classe Repositor non è menzionata nei documenti ufficiali che descrivono ROOM o i componenti dell'architettura Android
Jonathan

2
Il repository è una best practice suggerita per la separazione del codice e l'architettura, guarda questo esempio: codelabs.developers.google.com/codelabs/…
glisu

1
La funzione loadUsers()fondamentalmente chiamerà il repository per ottenere le informazioni dell'utente
CodeCameo

Risposte:


96

Presumo che tu stia utilizzando componenti dell'architettura Android . In realtà non importa ovunque tu chiami service, asynctask or handlerper aggiornare i dati. È possibile inserire i dati dal servizio o dall'asynctask utilizzando il postValue(..)metodo. La tua classe sarebbe simile a questa:

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

Come la usersè LiveData, Roomdatabase è responsabile di fornire i dati degli utenti, ovunque si inserisce.

Note: Nell'architettura simile a MVVM, il repository è principalmente responsabile del controllo e dell'estrazione di dati locali e remoti.


3
Ricevo "java.lang.IllegalStateException: impossibile accedere al database sul thread principale poiché" quando si chiama i miei metodi db come sopra, puoi dire cosa potrebbe essere sbagliato?
pcj

Sto usando evernote-job che aggiorna il database in background mentre sono sull'interfaccia utente. Ma LiveDatanon si aggiorna
Akshay Chordiya

users.postValue(mUsers);-> Ma il metodo postValue di MutableLiveData è in grado di accettare LiveData ???
Cheok Yan Cheng

2
Il mio errore è stato usare valueinvece di postValue. Grazie per la tua risposta.
Hesam

1
@pcj devi eseguire l'operazione room in un thread o abilitare le operazioni sul thread principale: google per ulteriori risposte.
kilokahn

46

Puoi usare il MutableLiveData<T>.postValue(T value)metodo dal thread in background.

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

19
Per ricordare, postValue () è protetto in LiveData ma pubblico in MutableLiveData
Long Ranger,

questo lavoro anche da un'attività in background e in situazioni in cui .setValue()non sarebbe consentito
davejoem

17

... nella funzione loadUsers () sto recuperando i dati in modo asincrono ... Se avvio un servizio per chiamare l'API dal metodo loadUsers (), come posso aggiornare la variabile MutableLiveData> utenti da quel servizio?

Se l'app sta recuperando i dati utente su un thread in background, sarà utile postValue (anziché setValue ).

Nel metodo loadData c'è un riferimento all'oggetto MutableLiveData "users". Il metodo loadData recupera anche alcuni nuovi dati utente da qualche parte (ad esempio, un repository).

Ora, se l'esecuzione è su un thread in background, MutableLiveData.postValue () si aggiorna all'esterno degli osservatori dell'oggetto MutableLiveData.

Forse qualcosa del genere:

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

}

Il getUsers()metodo del repository potrebbe chiamare un'API per i dati che è asincrona (potrebbe avviare un servizio o un'attività asincrona per questo) in tal caso come può restituire la lista dalla sua dichiarazione di ritorno?
CodeCameo

Forse potrebbe prendere l'oggetto LiveData come argomento. (Qualcosa come repository.getUsers (utenti)). Quindi il metodo del repository chiamerà lo stesso users.postValue. E, in tal caso, il metodo loadUsers non avrebbe nemmeno bisogno di un thread in background.
albert c braun

1
Grazie per la risposta .... tuttavia, sto usando un Room DB per l'archiviazione e DAO restituisce un LiveData <Object> ... come faccio a convertire LiveData in MutableLiveData <Object>?
kilokahn

Non credo che il DAO di Room sia realmente destinato a trattare oggetti MutableLiveData. Il DAO ti sta notificando una modifica al database sottostante, ma, se vuoi cambiare il valore nel DB, dovresti chiamare i metodi del DAO. Forse anche la discussione qui è utile: stackoverflow.com/questions/50943919/…
albert c braun

3

Dai un'occhiata alla guida all'architettura Android che accompagna i nuovi moduli di architettura come LiveDatae ViewModel. Discutono questo problema esatto in profondità.

Nei loro esempi non lo mettono in un servizio. Dai un'occhiata a come lo risolvono utilizzando un modulo "repository" e Retrofit. Le appendici in fondo includono esempi più completi tra cui la comunicazione dello stato della rete, la segnalazione di errori, ecc.


2

Se stai chiamando la tua API in Repository,

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

In ViewModel

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

In Attività / Frammento

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

Nota: utilizzando JAVA_1.8 lambda qui, puoi usarlo senza

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.