web-dev-qa-db-fra.com

Comment exclure un élément d'une requête Firestore?

J'ai une collection d'utilisateurs et je veux interroger tous les utilisateurs de la base de données et les afficher dans un RecyclerView sauf un, mine . Voici mon schéma de base de données:

users [collection]
  - uid [document]
     - uid: "fR5bih7SysccRu2Gu9990TeSSyg2"
     - username: "John"
     - age: 22
  - //other users

Comment interroger la base de données de la manière suivante:

String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
Query q = db.collection("users").whereNotEqualTo("uid", uid);

J'ai donc besoin que cet objet de requête soit passé à un objet FirestoreRecyclerOptions afin d'afficher tous les autres utilisateurs dans RecyclerView.

Est-ce seulement possible? Si non, comment puis-je résoudre ce problème? Merci!

Modifier:

options = new FirestoreRecyclerOptions.Builder<UserModel>()
        .setQuery(query, new SnapshotParser<UserModel>() {
            @NonNull
            @Override
            public UserModel parseSnapshot(@NonNull DocumentSnapshot snapshot) {
                UserModel userModel = documentSnapshot.toObject(UserModel.class);
                if (!userModel.getUid().equals(uid)) {
                    return userModel;
                } else {
                    return new UserModel();
                }
            }
        }).build();
6
Ioana P.

Après des jours et des jours de lutte avec ce problème, j'ai finalement trouvé la réponse. Je ne pourrais pas résoudre ce problème sans l'aide de @Raj. Merci beaucoup @ Raj pour la patience et les conseils.

Tout d’abord, selon la réponse fournie par @Frank van Puffelen dans sa réponse de post , j’ai arrêté de chercher une solution qui puisse m'aider à transmettre deux requêtes à un seul adaptateur.

Dans cette question, tout ce que je voulais réaliser était d'interroger la base de données pour obtenir tous les utilisateurs sauf un, moi. Donc, comme nous ne pouvons pas combiner deux requêtes en une seule instance, j'ai constaté que nous pouvions combiner le résultat des deux requêtes. J'ai donc créé deux requêtes:

FirebaseFirestore db = FirebaseFirestore.getInstance();
Query firstQuery = db.collection("users").whereLessThan("uid", uid);
Query secondQuery = db.collection("users").whereGreaterThan("uid", uid);

J'ai une classe UserModel (POJO) pour mon objet utilisateur. J'ai trouvé pas un, mais deux façons de résoudre le problème. La première consiste à interroger la base de données pour obtenir tous les objets utilisateur correspondant au premier critère et les ajouter à une liste. Après cela, interrogez à nouveau la base de données et récupérez les autres objets utilisateur correspondant au deuxième critère et ajoutez-les à la liste same. Maintenant, j'ai une liste qui contient tous les utilisateurs dont j'ai besoin sauf un, celui avec cet identifiant particulier issu des requêtes. C'est le code pour les futurs visiteurs:

firstQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<QuerySnapshot> task) {
        List<UserModel> list = new ArrayList<>();
        if (task.isSuccessful()) {
            for (DocumentSnapshot document : task.getResult()) {
                UserModel userModel = document.toObject(UserModel.class);
                list.add(userModel);
            }

            secondQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {
                        for (DocumentSnapshot document : task.getResult()) {
                            UserModel userModel = document.toObject(UserModel.class);
                            list.add(userModel);
                        }

                        //Use the list of users
                    }
                }
            });
        }
    }
});

La deuxième approche serait beaucoup plus courte parce que j'utilise Tasks.whenAllSuccess() comme ceci:

Task firstTask = firstQuery.get();
Task secondTask = secondQuery.get();

Task combinedTask = Tasks.whenAllSuccess(firstTask, secondTask).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
        @Override
        public void onSuccess(List<Object> list) {
            //This is the list that I wanted
        }
});
5
Ioana P.

Firestore ne supporte pas l'opération not equal to. Vous devez donc filtrer les données côté client. Étant donné que dans votre cas, vous ne disposez que d'un élément supplémentaire, vous pouvez le filtrer.

Pour cela, vous devrez peut-être créer votre propre implémentation Recycler. Lorsque vous ajoutez des données à la couche de données de l'adaptateur Recycler, vous limitez les données chaque fois que cela correspond à votre condition !=.

Je n'ai pas encore exploré la base de données d'implémentation de Recycler fournie, je ne peux donc pas affirmer qu'elle prend en charge la manipulation de données vers des données d'adaptateur.

Voici une bonne ressource pour commencer à mettre en œuvre la vue Recycler: https://www.androidhive.info/2016/01/Android-working-with-recycler-view/

4
Umar Hussain

Selon la documentation officielle de Firestore: -

Cloud Firestore ne prend pas en charge le type de requête suivant:

Requêtes avec une clause! =. Dans ce cas, vous devez scinder la requête dans une requête supérieure à et une requête inférieure à. Par exemple, bien que la clause de requête où ("age", "! =", "30") n'est pas supportée, vous pouvez obtenir le même jeu de résultats en combinant deux requêtes, une avec la clause où ("âge", "<", "30") et un avec la clause où ("âge", ">", 30).

Si vous utilisez FirestoreRecyclerAdapter, FirestoreRecyclerOptions acceptera directement la requête à l'aide de la méthode setQuery () et ne vous autorisera donc pas à effectuer de filtrage côté client. 

Si vous essayez d'appliquer des filtres dans onBindViewHolder () lors de la définition des données, des éléments vides risquent de se produire dans la vue du recycleur. Pour résoudre ce problème, reportez-vous à la méthode 2.

La solution possible à votre problème serait donc de créer un champ entier dans votre collection d'utilisateurs sous chaque document. Par exemple:-

users [collection]
  - uid [document]
     - uid: "fR5bih7SysccRu2Gu9990TeSSyg2"
     - username: "John"
     - age: 22
     - check: 100

En cela, j'ai créé une variable "check" dont la valeur est 100. Vous devez donc définir la valeur "check" dans tous les autres documents avec une valeur inférieure à 100 . Vous pouvez désormais effectuer facilement une requête qui recherche les documents avec check <100 comme:-

Query q = db.collection("users").whereLessThan("check", 100);

Cela permettra de récupérer tous vos documents sauf celui que vous ne voulez pas. Et tout en définissant les données, vous pouvez définir d’autres paramètres en ignorant la variable de contrôle.

Méthode 2 (filtrage côté client)

Nous pouvons appliquer une méthode de contrôle dans la méthode onBindViewHolder () qui, si l'identifiant récupéré correspond à l'identifiant de l'utilisateur actuel, définissez la hauteur de la vue Recycler sur 0dp. Comme:-

ViewUserAdapter.Java

public class ViewUserAdapter extends FirestoreRecyclerAdapter<User, ViewUserAdapter.ViewUserHolder>
{
    String uid;
    FirebaseAuth auth;

    public ViewUserAdapter(@NonNull FirestoreRecyclerOptions<User> options)
    {
        super(options);
        auth = FirebaseAuth.getInstance();
        uid = auth.getCurrentUser().getUid();
    }

    @Override
    protected void onBindViewHolder(@NonNull ViewUserHolder holder, int position, @NonNull User model)
    {
        DocumentSnapshot snapshot =  getSnapshots().getSnapshot(position);
        String id = snapshot.getId();

        if(uid.equals(id))
        {
            RecyclerView.LayoutParams param = (RecyclerView.LayoutParams)holder.itemView.getLayoutParams();
            param.height = 0;
            param.width = LinearLayout.LayoutParams.MATCH_PARENT;
            holder.itemView.setVisibility(View.VISIBLE);

        }
        else
        {
            holder.tvName.setText(model.name);
            holder.tvEmail.setText(model.email);
            holder.tvAge.setText(String.valueOf(model.age));
        }
    }
}
3
Raj

La solution la plus simple consisterait à utiliser un PagedListAdapter et à créer un DataSource personnalisé pour les requêtes Firestore. Dans DataSource, Query peut être transformé en un tableau ou une liste de tableaux dans lequel vous pouvez facilement supprimer votre élément avant d'ajouter les données à la méthode callback.onResult(...)

J'ai utilisé une solution similaire pour traiter les données après une requête Firestore afin de filtrer et de trier par un attribut time, puis de re-trier par un attribut de score de qualité dans le client avant de transmettre les données à callback.onResult(...).

Documentation

Exemple de source de données

class ContentFeedDataSource() : ItemKeyedDataSource<Date, Content>() {

override fun loadBefore(params: LoadParams<Date>, callback: LoadCallback<Content>) {}

override fun getKey(item: Content) = item.timestamp

override fun loadInitial(params: LoadInitialParams<Date>, callback: LoadInitialCallback<Content>) {
    FirestoreCollections.contentCollection
            .collection(FirestoreCollections.ALL_COLLECTION)
            .orderBy(Constants.TIMESTAMP, Query.Direction.DESCENDING)
            .whereGreaterThanOrEqualTo(Constants.TIMESTAMP, DateAndTime.getTimeframe(WEEK))
            .limit(params.requestedLoadSize.toLong())
            .get().addOnCompleteListener {
                val items = arrayListOf<Content?>()
                for (document in it.result.documents) {
                    val content = document.toObject(Content::class.Java)
                    items.add(content)
                }
                callback.onResult(items.sortedByDescending { it?.qualityScore })
            }
}

override fun loadAfter(params: LoadParams<Date>, callback: LoadCallback<Content>) {
    FirestoreCollections.contentCollection
            .collection(FirestoreCollections.ALL_COLLECTION)
            .orderBy(Constants.TIMESTAMP, Query.Direction.DESCENDING)
            .startAt(params.key)
            .whereGreaterThanOrEqualTo(Constants.TIMESTAMP, DateAndTime.getTimeframe(WEEK))
            .limit(params.requestedLoadSize.toLong())
            .get().addOnCompleteListener {
                val items = arrayListOf<Content?>()
                for (document in it.result.documents) {
                    val content = document.toObject(Content::class.Java)
                    items.add(content)
                }
                val sortedByQualityScore = ArrayList(items.sortedByDescending { it?.qualityScore })
                callback.onResult(sortedByQualityScore)
                sortedByQualityScore.clear()
            }
}
}
0
Adam Hurwitz

Filtrage côté client plus simple et antérieur (lorsque vous ajoutez des éléments à votre liste):

  1. Obtenez l'ID de l'utilisateur actuel en utilisant la méthode standard de Firestore.
  2. Obtenez le nom du document pour tous les utilisateurs de votre collection d'utilisateurs.
  3. Avant d'ajouter l'utilisateur à Votre liste RecyclerView, vérifiez que l'utilisateur qu'il est sur le point d'ajouter à votre liste n'est pas l'utilisateur actuel. 

Une fois que c'est fait, vous pouvez utiliser la méthode "not equals" du côté client et ne pas entrer dans des problèmes Firestore. Un autre avantage est que vous n'avez pas à manipuler votre adaptateur ni à masquer la vue d'un élément de liste que vous ne voulez pas dans le recycleur.

public void getUsers(final ArrayList<Users> usersArrayList, final Adapter adapter) {

    CollectionReference usersCollectionRef = db.collection("users");

    Query query = usersCollectionRef
            .whereEqualTo("is_onboarded", true);

    query.get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {
                        for (QueryDocumentSnapshot document : task.getResult()) {

                            final String otherUserID = document.getId();

                             FirebaseUser user = mAuth.getCurrentUser();
                             String currentUserID = user.getUid();

                            if (!otherUserID.equals(currentUserId)) {

                              usersArrayList.add(new User(otherUserID));
                              adapter.notifyDataSetChanged(); //Ensures users are visible immediately
                                            }
                                        } else {
                                            Log.d(TAG, "get failed with ", task.getException());
                                        }
                                    }
                                });
                            }

                        }
                    } else {
                        Log.d(TAG, "Error getting documents: ", task.getException());
                    }
                }
            });
}
0
The Fluffy T Rex