android firebase : NOT EQUAL query in Firebase Dat

2020-08-01 08:28发布

问题:

I have users object as below:

{
  "deviceTokens" : {
    "SgJd7o1hK7YbJnygnYWvaz4qkr42" : {
      "c-sfcTB3iEA:APA91bEvuzhX1nlrhoxVmR69lvwu0H1zyRjOXd5b14LngObuj8WeHkmlel6AOyrU7B2alBO1vW0pIO4gXGurvz5llCloyYpQv7vCq0_3fVEhmrIGfJ2S13TiXqVLJgWUCs95_AYZlysj" : true,
      "cnQdyuxBgz0:APA91bFZTPEFrl1Vf9Nw9ZvdtNsW1ll3YYq8WIwT799NVTp6S281v56UVJ3lo0kDK_rFZfoHuarX9Qb8NL8TJXi9A_jH4RhbDp3ecbW-tHdrYR06PwYdSiMfRjIxJOuPtHuqy0TNOw4K" : true,
      "d3D81crwy0E:APA91bEoAeWiOwm73bt2DvNNdv_NUn0PwjQcLa6z8ZYjDyuHWsNeDPVDCXOATKB5AFoBb7iOW6kgzU41oYFqHYek1Cngv6ArsrZO0crq_bV3PzfQxKvRN8xYX1_WryaR2tpAVP_4czgx" : true,
      "dHBaxzKqhMw:APA91bGmQ5KhxiaE48_IQHEJVpvpvQdXUgAd_a1reJiyh4Z5W6dhNZB4KesaQuiRrj-VLxdCvxIroH3a-75zUaYd6_yLtoCzNfNGT8EWqUrtC3FSibOA_cwPye09C001zfFe_pHv6Wph" : true,
      "dz8wf231cXE:APA91bEAmfPu4QWRstKE5kqtN-UtgNpOIG7KWxsjFB1oIOp9cKhoWfbnpsvMTd3YgNw09jVNK6lUgKXMNt4AND0cZGcepkqQyZTDNh5KA5SU9KbbD7MJzxGhwGhGlFV-4hiOOhumoRxs" : true
    }
  },
  "users" : {
    "SgJd7o1hK7YbJnygnYWvaz4qkr42" : {
      "displayName" : "Chintan Soni",
      "email" : "chintansoni202@gmail.com",
      "photoUrl" : "https://lh5.googleusercontent.com/-JNfSscqwEng/AAAAAAAAAAI/AAAAAAAADbs/dj7t_7AYlzo/s96-c/photo.jpg"
    }
  }
}

I am able to query Users object using:

mDatabaseReference = FirebaseDatabase.getInstance().getReference().child("users");

I wonder if there is anyway to query inequality. I want to query such that User's child's key doesn't match my FirebaseUser UID, means I want to fetch records of Users except mine.

回答1:

Actually, I didn't wanted to create my own adapter, because I believed that FirebaseUI would not have such a limitation, but I was wrong. Anyways, this is how I approached:

First of all fetch all the data from the User Object from FirebaseDatabase and then, filter on our end and finally, set that data:

FirebaseDatabase.getInstance().getReference().child("users")
   .addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            Iterable<DataSnapshot> list = dataSnapshot.getChildren();

            // Getting current user Id
            String uid = getCurrentUserId();

            // Filter User
            List<User> userList = new ArrayList<>();
            for (DataSnapshot dataSnapshot1 : list) {
                if (!dataSnapshot1.getKey().equals(uid)) {
                    userList.add(dataSnapshot1.getValue(User.class));
                }
            }

            // Setting data
            mBaseRecyclerAdapter.setItems(userList);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    });

I have created my own generic recycler adapter here: https://gist.github.com/chintansoni202/3c61aea787ae4bd49f26adee9dd40a08



回答2:

One way to solve this and still use FirebaseRecyclerAdapter from FirebaseUI is to extend FirebaseArray like this:

import com.firebase.ui.database.FirebaseArray;
import com.firebase.ui.database.SnapshotParser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.Query;

import java.util.HashSet;
import java.util.Set;

public class FilterableFirebaseArray extends FirebaseArray {

    private Set<String> excludes = new HashSet<>();

    public FilterableFirebaseArray(Query query, Class aClass) {
        super(query, aClass);
    }

    public FilterableFirebaseArray(Query query, SnapshotParser parser) {
        super(query, parser);
    }

    public void addExclude(String key) {
        excludes.add(key);
    }

    public void removeExclude(String key) {
        excludes.remove(key);
    }

    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildKey) {
        if (!excludes.contains(snapshot.getKey())) {
            super.onChildAdded(snapshot, excludes.contains(previousChildKey)? null : previousChildKey);
        }
    }

    @Override
    public void onChildChanged(DataSnapshot snapshot, String previousChildKey) {
        if (!excludes.contains(snapshot.getKey())) {
            super.onChildChanged(snapshot, excludes.contains(previousChildKey)? null : previousChildKey);
        }
    }

    @Override
    public void onChildMoved(DataSnapshot snapshot, String previousChildKey) {
        if (!excludes.contains(snapshot.getKey())) {
            super.onChildMoved(snapshot, excludes.contains(previousChildKey)? null : previousChildKey);
        }
    }

    @Override
    public void onChildRemoved(DataSnapshot snapshot) {
        if (!excludes.contains(snapshot.getKey())) {
            super.onChildRemoved(snapshot);
        }
    }

}

…and then use it like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    RecyclerView userListView = (RecyclerView) findViewById(R.id.userList);
    userListView.setLayoutManager(new LinearLayoutManager(this));

    DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
    Query query = ref.orderByValue();
    ClassSnapshotParser parser = new ClassSnapshotParser<UserProfile>(UserProfile.class);
    FilterableFirebaseArray filterableFirebaseArray = new FilterableFirebaseArray(query, parser);

    filterableFirebaseArray.addExclude(FirebaseAuth.getInstance().getCurrentUser().getUid());

    FirebaseRecyclerAdapter mAdapter = new FirebaseRecyclerAdapter<UserProfile, UserListEntryHolder>(
            filterableFirebaseArray,
            R.layout.userlist_entry,
            UserListEntryHolder.class) {
        @Override
        protected void populateViewHolder(UserListEntryHolder viewHolder, UserProfile model, int position) {
            viewHolder.bind(model);
        }
    };
    userListView.setAdapter(mAdapter);
}

The "magic" happens in the overridden onChildAdded method where we ignore children identified by the specified keys. In this example I simply add the UID of the current user to the exclusion list. As this matches my database design.

You could easily extend the functionality to ignore children with specific properties.

Bonus: If you prefer the Kotlin way (like me) this is how you'd do it:

import com.firebase.ui.database.FirebaseArray
import com.firebase.ui.database.SnapshotParser
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.Query

class FilterableFirebaseArray<T> : FirebaseArray<T> {

    val excludedKeys = HashSet<String?>()

    constructor(query: Query, tClass: Class<T>) : super(query, tClass)
    constructor(query: Query, parser: SnapshotParser<T>) : super(query, parser)

    override fun onChildAdded(snapshot: DataSnapshot, previousChildKey: String?) {
        if (!excludedKeys.contains(snapshot.key))
            super.onChildAdded(snapshot, if (excludedKeys.contains(previousChildKey)) null else previousChildKey)
    }

    override fun onChildChanged(snapshot: DataSnapshot, previousChildKey: String?) {
        if (!excludedKeys.contains(snapshot.key))
            super.onChildChanged(snapshot, previousChildKey)
    }

    override fun onChildMoved(snapshot: DataSnapshot, previousChildKey: String?) {
        if (!excludedKeys.contains(snapshot.key))
            super.onChildMoved(snapshot, previousChildKey)
    }

    override fun onChildRemoved(snapshot: DataSnapshot) {
        if (!excludedKeys.contains(snapshot.key))
            super.onChildRemoved(snapshot)
    }

}

…and then use it like this:

override fun onStart() {
    super.onStart()
    if (currentUser != null) {
        userList.adapter = getAdapter()
    }
}

private fun getAdapter(): FirebaseRecyclerAdapter<UserProfile, UserListEntryHolder> {

    val query = userProfilesReference.orderByValue()
    val parser = ClassSnapshotParser<UserProfile>(UserProfile::class.java)
    val filterableFirebaseArray = FilterableFirebaseArray<UserProfile>(query, parser)

    filterableFirebaseArray.excludes.add(currentUser?.uid)

    return object : FirebaseRecyclerAdapter<UserProfile, UserListEntryHolder>(
            filterableFirebaseArray,
            R.layout.userlist_entry,
            UserListEntryHolder::class.java) {
        public override fun populateViewHolder(entryHolder: UserListEntryHolder, userProfile: UserProfile, position: Int) {
            entryHolder.bind(userProfile)
        }
    }
}

Updated code to resolve IllegalArgumentExceptions related to onChildChanged, onChildMoved etc.