Comment mettre à jour LiveData d'un ViewModel à partir du service d'arrière-plan et de l'interface utilisateur de mise à jour

95

Récemment, j'explore l'architecture Android, qui a été récemment introduite par Google. D'après la documentation, j'ai trouvé ceci:

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'activité peut accéder à cette liste comme suit:

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

Ma question est, je vais faire ceci:

  1. dans la loadUsers()fonction je récupère les données de manière asynchrone où je vais d'abord vérifier la base de données (Room) pour ces données

  2. Si je n'obtiens pas les données là-bas, je passerai un appel API pour récupérer les données du serveur Web.

  3. Je vais insérer les données récupérées dans la base de données (Room) et mettre à jour l'interface utilisateur en fonction des données.

Quelle est l'approche recommandée pour ce faire?

Si je lance un Serviceappel à l'API à partir de la loadUsers()méthode, comment puis-je mettre à jour la MutableLiveData<List<User>> usersvariable à partir de cela Service?

CodeCameo
la source
8
Tout d'abord, il vous manque un référentiel. Votre ViewModel ne doit effectuer aucune tâche de chargement de données. En dehors de cela, depuis que vous utilisez Room, votre service n'a pas à mettre à jour les LiveData directement dans le ViewModel. Le service peut uniquement insérer des données dans la salle, tandis que votre ViewModelData doit être attaché uniquement à la salle et obtenir des mises à jour de la salle (après l'insertion des données par le service). Mais pour la meilleure architecture absolue, regardez l'implémentation de la classe NetworkBoundResource au bas de cette page: developer.android.com/topic/libraries/architecture/guide.html
Marko Gajić
merci pour la suggestion :)
CodeCameo
1
La classe des déposants n'est pas mentionnée dans les documents offocial décrivant ROOM ou les composants de l'architecture Android
Jonathan
2
Le référentiel est une bonne pratique suggérée pour la séparation et l'architecture du code, regardez cet exemple: codelabs.developers.google.com/codelabs/…
glisu
1
La fonction loadUsers()appellera essentiellement le dépôt pour obtenir les informations utilisateur
CodeCameo

Réponses:

96

Je suppose que vous utilisez des composants d'architecture Android . En fait, peu importe où vous appelez service, asynctask or handlerpour mettre à jour les données. Vous pouvez insérer les données du service ou de l'asynctask à l'aide de la postValue(..)méthode. Votre classe ressemblerait à ceci:

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

Comme l' usersest LiveData, la Roombase de données est responsable de fournir des données des utilisateurs partout où il est inséré.

Note: Dans l'architecture de type MVVM, le référentiel est principalement responsable de la vérification et de l'extraction des données locales et des données distantes.

androidcodehunter
la source
3
J'obtiens "java.lang.IllegalStateException: Impossible d'accéder à la base de données sur le thread principal car il" en appelant mes méthodes db comme ci-dessus, pouvez-vous dire ce qui pourrait ne pas aller?
pcj
J'utilise evernote-job qui met à jour la base de données en arrière-plan pendant que je suis sur l'interface utilisateur. Mais LiveDatane met pas à jour
Akshay Chordiya
users.postValue(mUsers);-> Mais, la méthode postValue de MutableLiveData est-elle capable d'accepter LiveData ???
Cheok Yan Cheng
2
Mon erreur était d'utiliser valueau lieu de postValue. Merci pour votre réponse.
Hesam
1
@pcj vous devez soit effectuer l'opération de la salle dans un thread, soit activer les opérations sur le thread principal - google pour plus de réponses.
kilokahn
46

Vous pouvez utiliser la MutableLiveData<T>.postValue(T value)méthode du thread d'arrière-plan.

private void loadUsers() {
    // do async operation to fetch users and use postValue() method
   users.postValue(listOfData)
}
Minhazur
la source
19
Pour rappel, postValue () est protégé dans LiveData mais public dans MutableLiveData
Long Ranger
ce travail même à partir d'une tâche d'arrière-plan et dans des situations où .setValue()ce ne serait pas autorisé
davejoem
17

... dans la fonction loadUsers () Je récupère les données de manière asynchrone ... Si je démarre un service pour appeler l'API à partir de la méthode loadUsers (), comment puis-je mettre à jour la variable MutableLiveData> users à partir de ce service?

Si l'application récupère les données utilisateur sur un thread d'arrière-plan, postValue (plutôt que setValue ) sera utile.

Dans la méthode loadData, il y a une référence à l'objet "users" MutableLiveData. La méthode loadData récupère également des données utilisateur fraîches quelque part (par exemple, un référentiel).

Maintenant, si l'exécution se fait sur un thread d'arrière-plan, MutableLiveData.postValue () met à jour les observateurs extérieurs de l'objet MutableLiveData.

Peut-être quelque chose comme ça:

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
la source
La getUsers()méthode du référentiel peut appeler une api pour les données asynchrones (peut démarrer un service ou une asynctask pour cela) dans ce cas, comment peut-elle renvoyer la liste à partir de son instruction return?
CodeCameo
Peut-être qu'il pourrait prendre l'objet LiveData comme argument. (Quelque chose comme repository.getUsers (utilisateurs)). Ensuite, la méthode du référentiel appellerait users.postValue elle-même. Et, dans ce cas, la méthode loadUsers n'aurait même pas besoin d'un thread d'arrière-plan.
albert c braun
1
Merci pour la réponse ... cependant, j'utilise une base de données de salle pour le stockage et le DAO renvoie un LiveData <Object> ... comment puis-je convertir le LiveData en un MutableLiveData <Object>?
kilokahn
Je ne pense pas que le DAO de Room soit vraiment destiné à traiter les objets MutableLiveData. Le DAO vous informe d'une modification apportée à la base de données sous-jacente, mais si vous souhaitez modifier la valeur dans la base de données, vous appelez les méthodes du DAO. Aussi peut-être que la discussion ici est utile: stackoverflow.com/questions/50943919/…
albert c braun
3

Jetez un œil au guide d'architecture Android qui accompagne les nouveaux modules d'architecture comme LiveDataet ViewModel. Ils discutent de cette question exacte en profondeur.

Dans leurs exemples, ils ne le mettent pas dans un service. Regardez comment ils le résolvent en utilisant un module «référentiel» et Retrofit. Les addenda en bas comprennent des exemples plus complets, notamment la communication de l'état du réseau, le signalement des erreurs, etc.

user1978019
la source
2

Si vous appelez votre API dans Repository,

Dans le référentiel :

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

Dans ViewModel

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

En activité / fragment

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

Remarque: en utilisant JAVA_1.8 lambda ici, vous pouvez l'utiliser sans

Shubham Agrawal
la source