web-dev-qa-db-fra.com

Comment puis-je obtenir une valeur de LiveData?

J'utilise Room pour la première fois. J'examine le concept LiveData. Je sais que nous pouvons récupérer des enregistrements de DB dans LiveData et avoir des serveurs attachés.

@Query("SELECT * FROM users")

<LiveData<List<TCUser>> getAll();

Mais j'effectue une synchronisation en arrière-plan, où je dois récupérer les données du serveur et les comparer avec les données de la table RoomDatabase appelées "utilisateurs", puis insérer, mettre à jour ou supprimer de la table des utilisateurs. Comment puis-je parcourir la liste LiveData avant d'entreprendre une action? Comme cela donne une erreur si je le mets en boucle.

OU ne devrais-je pas utiliser LiveData pour ce scénario?

Je suppose que je dois appeler

<LiveData<List<TCUser>> getAll().getValue()

Mais est-ce la bonne chose à faire? Voici un peu plus de code pour donner une idée de ce que j'essaie de faire:

List<User>serverUsers: Is the data received from a response from an API

private void updateUsers(List<User> serverUsers) {
    List<UserWithShifts> users = appDatabase.userDao().getAllUsers();
    HashMap<String, User> ids = new HashMap();
    HashMap<String, User> newIds = new HashMap();

    if (users != null) {
        for (UserWithShifts localUser : users) {
            ids.put(localUser.user.getId(), localUser.user);
        }
    }

    for (User serverUser : serverUsers) {
        newIds.put(serverUser.getId(), serverUser);

        if (!ids.containsKey(serverUser.getId())) {
            saveShiftForUser(serverUser);
        } else {
            User existingUser = ids.get(serverUser.getId());
            //If server data is newer than local
            if (DateTimeUtils.isLaterThan(serverUser.getUpdatedAt(), existingUser.getUpdatedAt())) {
                deleteEventsAndShifts(serverUser.getId());
                saveShiftForUser(serverUser);
            }
        }
    }

Où:

@Query("SELECT * FROM users")
List<UserWithShifts> getAllUsers();

La première ligne de updateUsers () est-elle la bonne façon de récupérer les données de la base de données à traiter avant d'en insérer de nouvelles ou devrait-elle le faire à la place

<LiveData<List<User>> getAll().getValue()

Merci,

9
user12865

Si je comprends bien votre architecture, updateUsers est à l'intérieur d'une AsyncTask ou similaire.

C'est mon approche proposée, qui consiste à peaufiner votre Dao pour une efficacité maximale. Vous avez écrit beaucoup de code pour prendre des décisions que vous pourriez demander à votre base de données de prendre.

Ce n'est pas non plus un code serré ou efficace, mais j'espère qu'il illustre une utilisation plus efficace de ces bibliothèques.

Fil d'arrière-plan (IntentService, AsyncTask, etc.):

/*
 * assuming this method is executing on a background thread
 */
private void updateUsers(/* from API call */List<User> serverUsers) {
    for(User serverUser : serverUsers){
        switch(appDatabase.userDao().userExistsSynchronous(serverUser.getId())){
            case 0: //doesn't exist
                saveShiftForUser(serverUser);
            case 1: //does exist
                UserWithShifts localUser = appDatabase.userDao().getOldUserSynchronous(serverUser.getId(), serverUser.getUpdatedAt());
                if(localUser != null){ //there is a record that's too old
                    deleteEventsAndShifts(serverUser.getId());
                    saveShiftForUser(serverUser);
                }
            default: //something happened, log an error
        }
    }
}

En cas d'exécution sur le thread d'interface utilisateur (activité, fragment, service):

/*
 * If you receive the IllegalStateException, try this code
 *
 * NOTE: This code is not well architected. I would recommend refactoring if you need to do this to make things more elegant.
 *
 * Also, RxJava is better suited to this use case than LiveData, but this may be easier for you to get started with
 */
private void updateUsers(/* from API call */List<User> serverUsers) {
    for(User serverUser : serverUsers){
        final LiveData<Integer> userExistsLiveData = appDatabase.userDao().userExists(serverUser.getId());
        userExistsLiveData.observe(/*activity or fragment*/ context, exists -> {
            userExistsLiveData.removeObservers(context); //call this so that this same code block isn't executed again. Remember, observers are fired when the result of the query changes.
            switch(exists){
                case 0: //doesn't exist
                    saveShiftForUser(serverUser);
                case 1: //does exist
                    final LiveData<UserWithShifts> localUserLiveData = appDatabase.userDao().getOldUser(serverUser.getId(), serverUser.getUpdatedAt());
                    localUserLiveData.observe(/*activity or fragment*/ context, localUser -> { //this observer won't be called unless the local data is out of date
                        localUserLiveData.removeObservers(context); //call this so that this same code block isn't executed again. Remember, observers are fired when the result of the query changes.
                        deleteEventsAndShifts(serverUser.getId());
                        saveShiftForUser(serverUser);
                    });
                default: //something happened, log an error
            }
        });
    }
}

Vous voudrez modifier le Dao pour toute approche que vous décidez d'utiliser

@Dao
public interface UserDao{
    /*
     * LiveData should be chosen for most use cases as running on the main thread will result in the error described on the other method
     */
    @Query("SELECT * FROM users")
    LiveData<List<UserWithShifts>> getAllUsers();

    /*
     * If you attempt to call this method on the main thread, you will receive the following error:
     *
     * Caused by: Java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.
     *  at Android.Arch.persistence.room.RoomDatabase.assertNotMainThread(AppDatabase.Java:XXX)
     *  at Android.Arch.persistence.room.RoomDatabase.query(AppDatabase.Java:XXX)
     *
     */
    @Query("SELECT * FROM users")
    List<UserWithShifts> getAllUsersSynchronous();

    @Query("SELECT EXISTS (SELECT * FROM users WHERE id = :id)")
    LiveData<Integer> userExists(String id);

    @Query("SELECT EXISTS (SELECT * FROM users WHERE id = :id)")
    Integer userExistsSynchronous(String id);

    @Query("SELECT * FROM users WHERE id = :id AND updatedAt < :updatedAt LIMIT 1")
    LiveData<UserWithShifts> getOldUser(String id, Long updatedAt);

    @Query("SELECT * FROM users WHERE id = :id AND updatedAt < :updatedAt LIMIT 1")
    UserWithShifts getOldUserSynchronous(String id, Long updatedAt);
}

Est-ce que cela résout votre problème?

REMARQUE: je n'ai pas vu vos méthodes saveShiftForUser ou deleteEventsAndShifts. L'insertion, la sauvegarde et la mise à jour sont effectuées de manière synchrone par Room. Si vous exécutez l'une des méthodes sur le thread principal (je suppose que c'est de là que vient votre erreur), vous devez créer un daoWrapper renvoyé par appDatabase comme suit:

public class UserDaoWrapper {
    private final UserDao userDao;

    public UserDaoWrapper(UserDao userDao) {
        this.userDao = userDao;
    }

    public LiveData<Long[]> insertAsync(UserWithShifts... users){
        final MutableLiveData<Long[]> keys = new MutableLiveData<>();
        HandlerThread ht = new HandlerThread("");
        ht.start();
        Handler h = new Handler(ht.getLooper());
        h.post(() -> keys.postValue(userDao.insert(users)));
        return keys;
    }

    public void updateAsync(UserWithShifts...users){
        HandlerThread ht = new HandlerThread("");
        ht.start();
        Handler h = new Handler(ht.getLooper());
        h.post(() -> {
            userDao.update(users);
        });
    }

    public void deleteAsync(User... users){
        HandlerThread ht = new HandlerThread("");
        ht.start();
        Handler h = new Handler(ht.getLooper());
        h.post(() -> {
            for(User e : users)
                userDao.delete(e.getId());
        });
    }
}
2
KG6ZVP